diff --git a/.devcontainer/server/container-compose-overrides.yml b/.devcontainer/server/container-compose-overrides.yml index 8f9e562e0a..db20390255 100644 --- a/.devcontainer/server/container-compose-overrides.yml +++ b/.devcontainer/server/container-compose-overrides.yml @@ -16,7 +16,7 @@ services: - ${UPLOAD_LOCATION:-upload-devcontainer-volume}${UPLOAD_LOCATION:+/photos}:/data - /etc/localtime:/etc/localtime:ro - pnpm_store_server:/buildcache/pnpm-store - - ../packages/plugins:/build/corePlugin + - ../packages/plugin-core:/build/plugins/immich-plugin-core immich-web: env_file: !reset [] immich-machine-learning: diff --git a/.github/workflows/build-mobile.yml b/.github/workflows/build-mobile.yml index bfbc7bd2e2..ea6de20da8 100644 --- a/.github/workflows/build-mobile.yml +++ b/.github/workflows/build-mobile.yml @@ -91,7 +91,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: github_token: ${{ steps.token.outputs.token }} @@ -159,14 +159,14 @@ jobs: - name: Comment APK download link on PR if: ${{ github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork }} - uses: mshick/add-pr-comment@8e4927817251f1ff60c001f04568532b38e0b4a0 # v3.11.0 + uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0 env: HEAD_SHA: ${{ github.event.pull_request.head.sha }} APK_URL: ${{ steps.upload-apk.outputs.artifact-url }} with: - github-token: ${{ steps.token.outputs.token }} - message-id: 'mobile-android-apk' - message: | + id: mobile-android-apk + token: ${{ steps.token.outputs.token }} + body: | 📱 **Android release APK (universal)** — `${{ env.HEAD_SHA }}` Download: ${{ env.APK_URL }} @@ -216,7 +216,7 @@ jobs: persist-credentials: false - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: github_token: ${{ steps.token.outputs.token }} @@ -231,7 +231,7 @@ jobs: run: mise //mobile:codegen:pigeon - name: Setup Ruby - uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0 + uses: ruby/setup-ruby@6aaa311d81eba98ae12eaffbcb63296ace0efcde # v1.307.0 with: ruby-version: '3.3' bundler-cache: true @@ -288,7 +288,6 @@ jobs: APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }} APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }} ENVIRONMENT: ${{ inputs.environment || 'development' }} - BUNDLE_ID_SUFFIX: ${{ inputs.environment == 'production' && '' || 'development' }} GITHUB_REF: ${{ github.ref }} FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT: 120 FASTLANE_XCODEBUILD_SETTINGS_RETRIES: 6 diff --git a/.github/workflows/check-openapi.yml b/.github/workflows/check-openapi.yml index 07c7505762..0ce156a0bc 100644 --- a/.github/workflows/check-openapi.yml +++ b/.github/workflows/check-openapi.yml @@ -24,7 +24,7 @@ jobs: persist-credentials: false - name: Check for breaking API changes - uses: oasdiff/oasdiff-action/breaking@26ccb332c67a45ca649de9faf60552ef1b8260d9 # v0.0.46 + uses: oasdiff/oasdiff-action/breaking@6147a58e5d1249a12f42fc864ab791d571a30015 # v0.0.47 with: base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json revision: open-api/immich-openapi-specs.json diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index fd4b7f1abe..42adf5c72a 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -43,7 +43,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: github_token: ${{ steps.token.outputs.token }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index f9e6dbfa2d..abfe2a77aa 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -57,7 +57,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3 + uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -70,7 +70,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3 + uses: github/codeql-action/autobuild@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -83,6 +83,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3 + uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4 with: category: '/language:${{matrix.language}}' diff --git a/.github/workflows/docs-build.yml b/.github/workflows/docs-build.yml index a85435ea5a..23f16c4c47 100644 --- a/.github/workflows/docs-build.yml +++ b/.github/workflows/docs-build.yml @@ -66,7 +66,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: github_token: ${{ steps.token.outputs.token }} diff --git a/.github/workflows/docs-deploy.yml b/.github/workflows/docs-deploy.yml index 083fa009eb..3b789e810d 100644 --- a/.github/workflows/docs-deploy.yml +++ b/.github/workflows/docs-deploy.yml @@ -131,7 +131,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: github_token: ${{ steps.token.outputs.token }} @@ -213,12 +213,11 @@ jobs: run: 'mise run //deployment:tf apply' - name: Comment - uses: actions-cool/maintain-one-comment@909842216bc8e8658364c572ec52100f4c2cc50a # v3.3.0 + uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0 if: ${{ steps.parameters.outputs.event == 'pr' }} with: + id: docs-pr-url token: ${{ steps.token.outputs.token }} number: ${{ fromJson(needs.checks.outputs.parameters).pr_number }} body: | 📖 Documentation deployed to [${{ steps.docs-output.outputs.subdomain }}](https://${{ steps.docs-output.outputs.subdomain }}) - emojis: 'rocket' - body-include: '' diff --git a/.github/workflows/docs-destroy.yml b/.github/workflows/docs-destroy.yml index 4186438d43..b1d75f241c 100644 --- a/.github/workflows/docs-destroy.yml +++ b/.github/workflows/docs-destroy.yml @@ -29,7 +29,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: github_token: ${{ steps.token.outputs.token }} @@ -44,9 +44,8 @@ jobs: run: 'mise run //deployment:tf destroy -- -refresh=false' - name: Comment - uses: actions-cool/maintain-one-comment@909842216bc8e8658364c572ec52100f4c2cc50a # v3.3.0 + uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0 with: + id: docs-pr-url token: ${{ steps.token.outputs.token }} - number: ${{ github.event.number }} delete: true - body-include: '' diff --git a/.github/workflows/fix-format.yml b/.github/workflows/fix-format.yml index e718c13792..23f23c1f4c 100644 --- a/.github/workflows/fix-format.yml +++ b/.github/workflows/fix-format.yml @@ -28,7 +28,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: github_token: ${{ steps.token.outputs.token }} diff --git a/.github/workflows/merge-translations.yml b/.github/workflows/merge-translations.yml index 685dfc6abe..ff00a16b6e 100644 --- a/.github/workflows/merge-translations.yml +++ b/.github/workflows/merge-translations.yml @@ -31,7 +31,7 @@ jobs: - name: Generate a token id: generate_token if: ${{ inputs.skip != true }} - uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} diff --git a/.github/workflows/org-zizmor.yml b/.github/workflows/org-zizmor.yml index 8510fd85b4..050e69b496 100644 --- a/.github/workflows/org-zizmor.yml +++ b/.github/workflows/org-zizmor.yml @@ -13,3 +13,4 @@ jobs: actions: read contents: read security-events: write + secrets: inherit diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index d4fe794913..4f6a7dc75b 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -62,7 +62,7 @@ jobs: ref: main - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: github_token: ${{ steps.token.outputs.token }} @@ -119,7 +119,7 @@ jobs: steps: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} diff --git a/.github/workflows/preview-label.yaml b/.github/workflows/preview-label.yaml index f4a03c3013..94fb9f797c 100644 --- a/.github/workflows/preview-label.yaml +++ b/.github/workflows/preview-label.yaml @@ -19,11 +19,11 @@ jobs: client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - - uses: mshick/add-pr-comment@8e4927817251f1ff60c001f04568532b38e0b4a0 # v3.11.0 + - uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0 with: - github-token: ${{ steps.token.outputs.token }} - message-id: 'preview-status' - message: 'Deploying preview environment to https://pr-${{ github.event.pull_request.number }}.preview.internal.immich.build/' + id: preview-status + token: ${{ steps.token.outputs.token }} + body: 'Deploying preview environment to https://pr-${{ github.event.pull_request.number }}.preview.internal.immich.build/' remove-label: runs-on: ubuntu-latest @@ -48,16 +48,16 @@ jobs: name: 'preview' }) - - uses: mshick/add-pr-comment@8e4927817251f1ff60c001f04568532b38e0b4a0 # v3.11.0 + - uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0 if: ${{ github.event.pull_request.head.repo.fork }} with: - github-token: ${{ steps.token.outputs.token }} - message-id: 'preview-status' - message: 'PRs from forks cannot have preview environments.' + id: preview-status + token: ${{ steps.token.outputs.token }} + body: 'PRs from forks cannot have preview environments.' - - uses: mshick/add-pr-comment@8e4927817251f1ff60c001f04568532b38e0b4a0 # v3.11.0 + - uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0 if: ${{ !github.event.pull_request.head.repo.fork }} with: - github-token: ${{ steps.token.outputs.token }} - message-id: 'preview-status' - message: 'Preview environment has been removed.' + id: preview-status + token: ${{ steps.token.outputs.token }} + body: 'Preview environment has been removed.' diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml index 9502d940ea..ce632fafb7 100644 --- a/.github/workflows/sdk.yml +++ b/.github/workflows/sdk.yml @@ -28,7 +28,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: github_token: ${{ steps.token.outputs.token }} diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index 10642fbd11..d347317793 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -61,7 +61,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: github_token: ${{ steps.token.outputs.token }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 97bccbc9ba..e16e6f059d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,25 +30,32 @@ jobs: filters: | i18n: - 'i18n/**' + - 'mise.toml' web: - 'web/**' - 'i18n/**' - 'packages/sdk/**' - 'pnpm-lock.yaml' + - 'mise.toml' server: - 'server/**' - 'pnpm-lock.yaml' + - 'mise.toml' cli: - 'packages/cli/**' - 'packages/sdk/**' - 'pnpm-lock.yaml' + - 'mise.toml' e2e: - 'e2e/**' - 'pnpm-lock.yaml' + - 'mise.toml' mobile: - 'mobile/**' + - 'mise.toml' machine-learning: - 'machine-learning/**' + - 'mise.toml' .github: - '.github/**' force-filters: | @@ -62,9 +69,6 @@ jobs: runs-on: ubuntu-latest permissions: contents: read - defaults: - run: - working-directory: ./server steps: - id: token uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0 @@ -79,12 +83,12 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: github_token: ${{ steps.token.outputs.token }} - name: Run ci-unit - run: mise run ci-unit + run: mise run //server:ci-unit cli-unit-tests: name: Unit Test CLI @@ -110,7 +114,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: github_token: ${{ steps.token.outputs.token }} @@ -141,7 +145,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: github_token: ${{ steps.token.outputs.token }} @@ -185,7 +189,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: github_token: ${{ steps.token.outputs.token }} @@ -223,7 +227,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: github_token: ${{ steps.token.outputs.token }} @@ -251,7 +255,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: github_token: ${{ steps.token.outputs.token }} @@ -301,7 +305,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: github_token: ${{ steps.token.outputs.token }} @@ -334,7 +338,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: github_token: ${{ steps.token.outputs.token }} @@ -380,7 +384,7 @@ jobs: cache-dependency-path: '**/pnpm-lock.yaml' - name: Setup packages - run: pnpm --filter "@immich/*" install --frozen-lockfile && pnpm --filter "@immich/*" build + run: pnpm --filter @immich/sdk --filter @immich/cli install --frozen-lockfile && pnpm --filter @immich/sdk --filter @immich/cli build - name: Run setup web run: pnpm install --frozen-lockfile && pnpm exec svelte-kit sync @@ -553,7 +557,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: github_token: ${{ steps.token.outputs.token }} @@ -590,7 +594,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: github_token: ${{ steps.token.outputs.token }} @@ -621,7 +625,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: github_token: ${{ steps.token.outputs.token }} @@ -672,13 +676,12 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: github_token: ${{ steps.token.outputs.token }} - name: Install server dependencies run: SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm --filter immich install --frozen-lockfile - - name: Run API generation run: mise //:open-api working-directory: open-api @@ -717,9 +720,6 @@ jobs: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 ports: - 5432:5432 - defaults: - run: - working-directory: ./server steps: - id: token uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0 @@ -734,25 +734,28 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1 + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: github_token: ${{ steps.token.outputs.token }} - name: Install server dependencies run: SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm install --frozen-lockfile + - name: Build plugins + run: mise //:plugins + - name: Build the app - run: pnpm build + run: mise //server:build - name: Run existing migrations - run: pnpm migrations:run + run: pnpm --filter immich migrations:run - name: Test npm run schema:reset command works - run: pnpm schema:reset + run: pnpm --filter immich schema:reset - name: Generate new migrations continue-on-error: true - run: pnpm migrations:generate src/TestMigration + run: pnpm --filter migrations:generate src/TestMigration - name: Find file changes uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4 @@ -768,7 +771,7 @@ jobs: run: | echo "ERROR: Generated migration files not up to date!" echo "Changed files: ${CHANGED_FILES}" - cat ./src/*-TestMigration.ts + cat ./server/src/*-TestMigration.ts exit 1 - name: Run SQL generation diff --git a/deployment/mise.lock b/deployment/mise.lock new file mode 100644 index 0000000000..fb7be27b99 --- /dev/null +++ b/deployment/mise.lock @@ -0,0 +1,65 @@ +# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html + +[[tools.opentofu]] +version = "1.11.6" +backend = "aqua:opentofu/opentofu" + +[tools.opentofu."platforms.linux-arm64"] +checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850" +url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz" + +[tools.opentofu."platforms.linux-arm64-musl"] +checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850" +url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz" + +[tools.opentofu."platforms.linux-x64"] +checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e" +url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz" + +[tools.opentofu."platforms.linux-x64-musl"] +checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e" +url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz" + +[tools.opentofu."platforms.macos-arm64"] +checksum = "sha256:62d7fa8539e13b444827aa0a3b90c5972da5c47e8f8882d9dcf2e430e78840c1" +url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_arm64.tar.gz" + +[tools.opentofu."platforms.macos-x64"] +checksum = "sha256:1408cdef1c380f914565e6b4bb70794c6b163f195fcb233357f3d6c5745906b6" +url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_amd64.tar.gz" + +[tools.opentofu."platforms.windows-x64"] +checksum = "sha256:27323f70c875b8251bfd7e61a4cffc3ebff4e56ed1e611b955016f0c7077367e" +url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_windows_amd64.tar.gz" + +[[tools.terragrunt]] +version = "1.0.3" +backend = "aqua:gruntwork-io/terragrunt" + +[tools.terragrunt."platforms.linux-arm64"] +checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz" + +[tools.terragrunt."platforms.linux-arm64-musl"] +checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz" + +[tools.terragrunt."platforms.linux-x64"] +checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz" + +[tools.terragrunt."platforms.linux-x64-musl"] +checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz" + +[tools.terragrunt."platforms.macos-arm64"] +checksum = "sha256:aacb5be2ca5475300cbce246dfbd8a45eb47510fbaa70fab8561c49ef5db03aa" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_arm64.tar.gz" + +[tools.terragrunt."platforms.macos-x64"] +checksum = "sha256:3133c2251e191aede8e3dd2a5b3aee2e91c5f08f88f117aee40eed9a24c8ef6b" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_amd64.tar.gz" + +[tools.terragrunt."platforms.windows-x64"] +checksum = "sha256:183b2745b4e04980a6bfa4450ff81956a12596ca22d70f7aaa793980f5b036db" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_windows_amd64.exe.tar.gz" diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index dfb876e6bd..350e82e767 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -74,7 +74,7 @@ services: - ${UPLOAD_LOCATION}/photos:/data - /etc/localtime:/etc/localtime:ro - pnpm_store_server:/buildcache/pnpm-store - - ../packages/plugins:/build/corePlugin + - ../packages/plugin-core:/build/plugins/immich-plugin-core env_file: - .env environment: diff --git a/docs/docs/developer/testing.md b/docs/docs/developer/testing.md index 219c33d1a1..d2f64b7824 100644 --- a/docs/docs/developer/testing.md +++ b/docs/docs/developer/testing.md @@ -18,7 +18,7 @@ make e2e Before you can run the tests, you need to run the following commands _once_: - `pnpm install` -- `pnpm --filter "@immich/*" build` +- `pnpm --filter @immich/sdk --filter @immich/cli build` - `mise //:open-api` Once the test environment is running, the e2e tests can be run via: diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 00a120b8b6..734317a302 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -10,7 +10,6 @@ const config = { url: 'https://docs.immich.app', baseUrl: '/', onBrokenLinks: 'throw', - onBrokenMarkdownLinks: 'warn', favicon: 'img/favicon.png', // GitHub pages deployment config. @@ -29,6 +28,9 @@ const config = { // Mermaid diagrams markdown: { mermaid: true, + hooks: { + onBrokenMarkdownLinks: 'warn', + }, }, themes: ['@docusaurus/theme-mermaid'], diff --git a/docs/mise.lock b/docs/mise.lock new file mode 100644 index 0000000000..dee476c431 --- /dev/null +++ b/docs/mise.lock @@ -0,0 +1,5 @@ +# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html + +[[tools.wrangler]] +version = "4.66.0" +backend = "npm:wrangler" diff --git a/docs/mise.toml b/docs/mise.toml index 32fcac5578..4c95e36173 100644 --- a/docs/mise.toml +++ b/docs/mise.toml @@ -28,4 +28,4 @@ run = "prettier --write ." run = "wrangler pages deploy build --project-name=${PROJECT_NAME} --branch=${BRANCH_NAME}" [tools] -wrangler = "4.66.0" +wrangler = "4.91.0" diff --git a/e2e/package.json b/e2e/package.json index 00868d001d..77003c03d7 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -32,7 +32,7 @@ "@playwright/test": "^1.44.1", "@socket.io/component-emitter": "^3.1.2", "@types/luxon": "^3.4.2", - "@types/node": "^24.12.2", + "@types/node": "^24.12.4", "@types/pg": "^8.15.1", "@types/pngjs": "^6.0.4", "@types/supertest": "^7.0.0", diff --git a/e2e/src/specs/maintenance/server/database-backups.e2e-spec.ts b/e2e/src/specs/maintenance/server/database-backups.e2e-spec.ts index b69bd099ed..e3bd98db28 100644 --- a/e2e/src/specs/maintenance/server/database-backups.e2e-spec.ts +++ b/e2e/src/specs/maintenance/server/database-backups.e2e-spec.ts @@ -2,7 +2,7 @@ import { LoginResponseDto, ManualJobName } from '@immich/sdk'; import { errorDto } from 'src/responses'; import { app, utils } from 'src/utils'; import request from 'supertest'; -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest'; describe('/admin/database-backups', () => { let cookie: string | undefined; @@ -13,6 +13,9 @@ describe('/admin/database-backups', () => { admin = await utils.adminSetup({ onboarding: false, }); + }); + + beforeEach(async () => { await utils.resetBackups(admin.accessToken); }); diff --git a/e2e/src/utils.ts b/e2e/src/utils.ts index 74c2832c3e..7e51b40f63 100644 --- a/e2e/src/utils.ts +++ b/e2e/src/utils.ts @@ -568,6 +568,8 @@ export const utils = { name: ManualJobName.BackupDatabase, }); + await utils.waitForQueueFinish(accessToken, 'backupDatabase'); + return utils.poll( () => request(app).get('/admin/database-backups').set('Authorization', `Bearer ${accessToken}`), ({ status, body }) => status === 200 && body.backups.length === 1, diff --git a/i18n/en.json b/i18n/en.json index 697aa7f2fa..97f4575567 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -22,13 +22,12 @@ "add_birthday": "Add a birthday", "add_endpoint": "Add endpoint", "add_exclusion_pattern": "Add exclusion pattern", - "add_filter": "Add filter", - "add_filter_description": "Click to add a filter condition", "add_location": "Add location", "add_more_users": "Add more users", "add_partner": "Add partner", "add_path": "Add path", "add_photos": "Add photos", + "add_step": "Add step", "add_tag": "Add tag", "add_to": "Add to…", "add_to_album": "Add to album", @@ -42,7 +41,6 @@ "add_to_shared_album": "Add to shared album", "add_upload_to_stack": "Add upload to stack", "add_url": "Add URL", - "add_workflow_step": "Add workflow step", "added_to_archive": "Added to archive", "added_to_favorites": "Added to favorites", "added_to_favorites_count": "Added {count, number} to favorites", @@ -733,6 +731,7 @@ "cannot_update_the_description": "Cannot update the description", "cast": "Cast", "cast_description": "Configure available cast destinations", + "change": "Change", "change_date": "Change date", "change_description": "Change description", "change_display_order": "Change display order", @@ -761,6 +760,7 @@ "check_corrupt_asset_backup_description": "Run this check only over Wi-Fi and once all assets have been backed-up. The procedure might take a few minutes.", "check_logs": "Check Logs", "checksum": "Checksum", + "choose": "Choose", "choose_matching_people_to_merge": "Choose matching people to merge", "city": "City", "cleanup_confirm_description": "Immich found {count} assets (created before {date}) safely backed up to the server. Remove the local copies from this device?", @@ -778,6 +778,7 @@ "clear": "Clear", "clear_all": "Clear all", "clear_all_recent_searches": "Clear all recent searches", + "clear_failed_count": "Clear failed ({count})", "clear_file_cache": "Clear File Cache", "clear_message": "Clear message", "clear_value": "Clear value", @@ -809,6 +810,7 @@ "comments_are_disabled": "Comments are disabled", "common_create_new_album": "Create new album", "completed": "Completed", + "configuration": "Configuration", "confirm": "Confirm", "confirm_admin_password": "Confirm Admin Password", "confirm_delete_face": "Are you sure you want to delete {name} face from the asset?", @@ -823,6 +825,7 @@ "contain": "Contain", "context": "Context", "continue": "Continue", + "control_bottom_app_bar_add_tags": "Add Tags", "control_bottom_app_bar_create_new_album": "Create new album", "control_bottom_app_bar_delete_from_immich": "Delete from Immich", "control_bottom_app_bar_delete_from_local": "Delete from device", @@ -894,6 +897,7 @@ "date_of_birth": "Date of birth", "date_of_birth_saved": "Date of birth saved successfully", "date_range": "Date range", + "date_time_original": "Date/Time Original", "day": "Day", "days": "Days", "deduplicate_all": "Deduplicate All", @@ -1074,6 +1078,7 @@ "failed_to_remove_product_key": "Failed to remove product key", "failed_to_reset_pin_code": "Failed to reset PIN code", "failed_to_stack_assets": "Failed to stack assets", + "failed_to_tag_assets": "Failed to tag assets", "failed_to_unstack_assets": "Failed to un-stack assets", "failed_to_update_notification_status": "Failed to update notification status", "incorrect_email_or_password": "Incorrect email or password", @@ -1193,11 +1198,13 @@ "export_as_json": "Export as JSON", "export_database": "Export Database", "export_database_description": "Export the SQLite database", + "exposure_time": "Exposure Time", "extension": "Extension", "external": "External", "external_libraries": "External Libraries", "external_network": "External network", "external_network_sheet_info": "When not on the preferred Wi-Fi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "f_number": "F-Number", "face_unassigned": "Unassigned", "failed": "Failed", "failed_count": "Failed: {count}", @@ -1215,7 +1222,6 @@ "features_setting_description": "Manage the app features", "file_name_or_extension": "File name or extension", "file_name_text": "File name", - "file_name_with_value": "File name: {file_name}", "file_size": "File size", "filename": "Filename", "filetype": "Filetype", @@ -1228,6 +1234,7 @@ "find_them_fast": "Find them fast by name with search", "first": "First", "fix_incorrect_match": "Fix incorrect match", + "focal_length": "Focal Length", "folder": "Folder", "folder_not_found": "Folder not found", "folders": "Folders", @@ -1348,6 +1355,7 @@ "ios_debug_info_no_sync_yet": "No background sync job has run yet", "ios_debug_info_processes_queued": "{count, plural, one {{count} background process queued} other {{count} background processes queued}}", "ios_debug_info_processing_ran_at": "Processing ran {dateTime}", + "iso": "ISO", "items_count": "{count, plural, one {# item} other {# items}}", "jobs": "Jobs", "json_editor": "JSON editor", @@ -1580,6 +1588,7 @@ "mobile_app": "Mobile App", "mobile_app_download_onboarding_note": "Download the companion mobile app using the following options", "model": "Model", + "modify_date": "Modify Date", "month": "Month", "more": "More", "motion": "Motion", @@ -1628,7 +1637,6 @@ "next": "Next", "next_memory": "Next memory", "no": "No", - "no_actions_added": "No actions added yet", "no_albums_found": "No albums found", "no_albums_message": "Create an album to organize your photos and videos", "no_albums_with_name_yet": "It looks like you do not have any albums with this name yet.", @@ -1645,7 +1653,6 @@ "no_exif_info_available": "No exif info available", "no_explore_results_message": "Upload more photos to explore your collection.", "no_favorites_message": "Add favorites to quickly find your best pictures and videos", - "no_filters_added": "No filters added yet", "no_libraries_message": "Create an external library to view your photos and videos", "no_local_assets_found": "No local assets found with this checksum", "no_location_set": "No location set", @@ -1658,6 +1665,7 @@ "no_results": "No results", "no_results_description": "Try a synonym or more general keyword", "no_shared_albums_message": "Create an album to share photos and videos with people in your network", + "no_steps": "No steps added yet", "no_uploads_in_progress": "No uploads in progress", "none": "None", "not_allowed": "Not allowed", @@ -1703,6 +1711,7 @@ "organize_into_albums": "Organize into albums", "organize_into_albums_description": "Put existing photos into albums using current sync settings", "organize_your_library": "Organize your library", + "orientation": "Orientation", "original": "original", "other": "Other", "other_devices": "Other devices", @@ -1794,6 +1803,8 @@ "play_original_video_setting_description": "Prefer playback of original videos rather than transcoded videos. If original asset is not compatible it may not playback correctly.", "play_transcoded_video": "Play transcoded video", "please_auth_to_access": "Please authenticate to access", + "plugin_method_filter_type": "Filter", + "plugin_method_filter_type_description": "This method can filter events and conditionally prevent subsequent steps from running", "port": "Port", "preferences_settings_subtitle": "Manage the app's preferences", "preferences_settings_title": "Preferences", @@ -1815,6 +1826,7 @@ "profile_drawer_readonly_mode": "Read-only mode enabled. Long-press the user avatar icon to exit.", "profile_image_of_user": "Profile image of {user}", "profile_picture_set": "Profile picture set.", + "projection_type": "Projection Type", "public_album": "Public album", "public_share": "Public Share", "purchase_account_info": "Supporter", @@ -2184,7 +2196,9 @@ "show_in_timeline": "Show in timeline", "show_in_timeline_setting_description": "Show photos and videos from this user in your timeline", "show_keyboard_shortcuts": "Show keyboard shortcuts", + "show_less": "Show less", "show_metadata": "Show metadata", + "show_more_fields": "{count, plural, one {Show # more field} other {Show # more fields}}", "show_or_hide_info": "Show or hide info", "show_password": "Show password", "show_person_options": "Show person options", @@ -2236,6 +2250,10 @@ "start_date_before_end_date": "Start date must be before end date", "state": "State", "status": "Status", + "step_delete": "Delete step", + "step_delete_confirm": "Are you sure you want to delete this step?", + "step_details": "Step details", + "steps": "Steps", "stop_casting": "Stop casting", "stop_motion_photo": "Stop Motion Photo", "stop_photo_sharing": "Stop sharing your photos?", @@ -2329,7 +2347,7 @@ "trash_page_title": "Trash ({count})", "trashed_items_will_be_permanently_deleted_after": "Trashed items will be permanently deleted after {days, plural, one {# day} other {# days}}.", "trigger": "Trigger", - "trigger_asset_uploaded": "Asset Uploaded", + "trigger_asset_uploaded": "Asset Upload", "trigger_asset_uploaded_description": "Triggered when a new asset is uploaded", "trigger_description": "An event that kicks off the workflow", "trigger_person_recognized": "Person Recognized", @@ -2369,7 +2387,6 @@ "unsupported_field_type": "Unsupported field type", "unsupported_file_type": "File {file} can't be uploaded because its file type {type} is not supported.", "untagged": "Untagged", - "untitled_workflow": "Untitled workflow", "up_next": "Up next", "update_location_action_prompt": "Update the location of {count} selected assets with:", "updated_at": "Updated", @@ -2461,6 +2478,7 @@ "welcome_to_immich": "Welcome to Immich", "width": "Width", "wifi_name": "Wi-Fi Name", + "workflow": "Workflow", "workflow_delete_prompt": "Are you sure you want to delete this workflow?", "workflow_deleted": "Workflow deleted", "workflow_description": "Workflow description", diff --git a/machine-learning/mise.lock b/machine-learning/mise.lock new file mode 100644 index 0000000000..b9788e603b --- /dev/null +++ b/machine-learning/mise.lock @@ -0,0 +1,72 @@ +# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html + +[[tools.python]] +version = "3.11.15" +backend = "core:python" + +[tools.python."platforms.linux-arm64"] +checksum = "sha256:243f794278eff6adba96ed3677ec6877175df84c25f140e17f09f9be82d0f12a" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260510/cpython-3.11.15+20260510-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz" +provenance = "github-attestations" + +[tools.python."platforms.linux-arm64-musl"] +checksum = "sha256:52b4c52094ff8b383a45c694acf4c5c0e883152be6d5229a35a8186ce907c6eb" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260510/cpython-3.11.15+20260510-aarch64-unknown-linux-musl-install_only_stripped.tar.gz" +provenance = "github-attestations" + +[tools.python."platforms.linux-x64"] +checksum = "sha256:171dffd8c0f66e8a0725364a7428015b22fc18dd298b24f541392e17dd0e561f" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260510/cpython-3.11.15+20260510-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz" +provenance = "github-attestations" + +[tools.python."platforms.linux-x64-musl"] +checksum = "sha256:2ac90fef8917ebd14826a6d667593a06cf0ae5f745ba9b1147dc086dd35f5284" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260510/cpython-3.11.15+20260510-x86_64-unknown-linux-musl-install_only_stripped.tar.gz" +provenance = "github-attestations" + +[tools.python."platforms.macos-arm64"] +checksum = "sha256:fdfc363b538662eb7441a14e06f72c4a992c56af7f401f5730ea5081f8f8ad6e" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260510/cpython-3.11.15+20260510-aarch64-apple-darwin-install_only_stripped.tar.gz" +provenance = "github-attestations" + +[tools.python."platforms.macos-x64"] +checksum = "sha256:5f1eb247cbca2c0ad5ccbf6d299a4f54b31b5c63b492d74c3531dc4344a42f88" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260510/cpython-3.11.15+20260510-x86_64-apple-darwin-install_only_stripped.tar.gz" +provenance = "github-attestations" + +[tools.python."platforms.windows-x64"] +checksum = "sha256:756d7f148498b8822f6aedf44a020613576f09983161f346ad36dcef6238cdc3" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260510/cpython-3.11.15+20260510-x86_64-pc-windows-msvc-install_only_stripped.tar.gz" +provenance = "github-attestations" + +[[tools.uv]] +version = "0.8.15" +backend = "aqua:astral-sh/uv" + +[tools.uv."platforms.linux-arm64"] +checksum = "sha256:23ea21a05c62c4c307ce691f29bff2f15c94c4f07f2b83d9b356f0664bc8b3a2" +url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-aarch64-unknown-linux-musl.tar.gz" + +[tools.uv."platforms.linux-arm64-musl"] +checksum = "sha256:23ea21a05c62c4c307ce691f29bff2f15c94c4f07f2b83d9b356f0664bc8b3a2" +url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-aarch64-unknown-linux-musl.tar.gz" + +[tools.uv."platforms.linux-x64"] +checksum = "sha256:d0fec58f3124e05e0a1af0f6541abfce4333253cdaf23c7b6bb2e6128bf138ea" +url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-x86_64-unknown-linux-musl.tar.gz" + +[tools.uv."platforms.linux-x64-musl"] +checksum = "sha256:d0fec58f3124e05e0a1af0f6541abfce4333253cdaf23c7b6bb2e6128bf138ea" +url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-x86_64-unknown-linux-musl.tar.gz" + +[tools.uv."platforms.macos-arm64"] +checksum = "sha256:103367962c5cb00bf7370d84cbaa3fec5a9807be9cc833ea9d8eea400c119fa2" +url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-aarch64-apple-darwin.tar.gz" + +[tools.uv."platforms.macos-x64"] +checksum = "sha256:2bbef70982e97dfc36454de173f35ec1a5e83ae11e3885df6a50db3fd76171cb" +url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-x86_64-apple-darwin.tar.gz" + +[tools.uv."platforms.windows-x64"] +checksum = "sha256:459d95892a5cc5c21779532f4f41b9238594b79e312a5142da2148ecfa10e705" +url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-x86_64-pc-windows-msvc.zip" diff --git a/mise.lock b/mise.lock new file mode 100644 index 0000000000..440177aab2 --- /dev/null +++ b/mise.lock @@ -0,0 +1,332 @@ +# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html + +[[tools."aqua:flutter/flutter"]] +version = "3.41.9" +backend = "aqua:flutter/flutter" + +[[tools.flutter]] +version = "3.41.9-stable" +backend = "asdf:flutter" + +[[tools."github:CQLabs/homebrew-dcm"]] +version = "1.37.0" +backend = "github:CQLabs/homebrew-dcm" + +[tools."github:CQLabs/homebrew-dcm"."platforms.linux-arm64"] +checksum = "sha256:253da2512b149913dfe345bf9a62a79acb2d730f66e71162ba4a92dfc4224b82" +url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-linux-arm-release.zip" +url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543838" + +[tools."github:CQLabs/homebrew-dcm"."platforms.linux-arm64-musl"] +checksum = "sha256:253da2512b149913dfe345bf9a62a79acb2d730f66e71162ba4a92dfc4224b82" +url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-linux-arm-release.zip" +url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543838" + +[tools."github:CQLabs/homebrew-dcm"."platforms.linux-x64"] +checksum = "sha256:477e086d4099c12f21e5ccd83b005d5fb945dd4cac4fd127fd9a08d7649af1cf" +url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-linux-x64-release.zip" +url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543797" + +[tools."github:CQLabs/homebrew-dcm"."platforms.linux-x64-musl"] +checksum = "sha256:477e086d4099c12f21e5ccd83b005d5fb945dd4cac4fd127fd9a08d7649af1cf" +url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-linux-x64-release.zip" +url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543797" + +[tools."github:CQLabs/homebrew-dcm"."platforms.macos-arm64"] +checksum = "sha256:30bede64367d09067093cc57af6ec9496d7717898138ded5cb98a16ac8dd9d93" +url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-macos-arm-release.zip" +url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543757" + +[tools."github:CQLabs/homebrew-dcm"."platforms.macos-x64"] +checksum = "sha256:e56cb99872be7445a4de1d37e5438ca70e3bcd83be7a2b9b385e3538881f8068" +url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-macos-x64-release.zip" +url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543727" + +[tools."github:CQLabs/homebrew-dcm"."platforms.windows-x64"] +checksum = "sha256:f133470daa3fb0427f039b424392af7e917d7e7db6b556aa2a968ab0e31587da" +url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-windows-release.zip" +url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543660" + +[[tools."github:extism/cli"]] +version = "1.6.3" +backend = "github:extism/cli" + +[tools."github:extism/cli"."platforms.linux-arm64"] +checksum = "sha256:d92f830c9be39637569feacb04e9750c28848df6d9a219db94152a9b4eb9452b" +url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-linux-arm64.tar.gz" +url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694030" + +[tools."github:extism/cli"."platforms.linux-arm64-musl"] +checksum = "sha256:d92f830c9be39637569feacb04e9750c28848df6d9a219db94152a9b4eb9452b" +url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-linux-arm64.tar.gz" +url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694030" + +[tools."github:extism/cli"."platforms.linux-x64"] +checksum = "sha256:34e7ae9bfded6e2c32dee83f70a4e50d34f9d3e80d1762b09625fe82e214d02d" +url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-linux-amd64.tar.gz" +url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694025" + +[tools."github:extism/cli"."platforms.linux-x64-musl"] +checksum = "sha256:34e7ae9bfded6e2c32dee83f70a4e50d34f9d3e80d1762b09625fe82e214d02d" +url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-linux-amd64.tar.gz" +url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694025" + +[tools."github:extism/cli"."platforms.macos-arm64"] +checksum = "sha256:b4ddbc575b5ac000115247f781723f9b9f284ed87b29c600539d72161b5b29fc" +url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-darwin-arm64.tar.gz" +url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694029" + +[tools."github:extism/cli"."platforms.macos-x64"] +checksum = "sha256:9a2f71b6e6009685a622cc3084e52d2a1a8e23c98d29ffa72e666e9dc699855f" +url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-darwin-amd64.tar.gz" +url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694026" + +[tools."github:extism/cli"."platforms.windows-x64"] +checksum = "sha256:47e4ed2782445b2b08a4d1ac127211588f8b4d1fc25fd6481d4cb65151b5213c" +url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-windows-amd64.zip" +url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694035" + +[[tools."github:extism/js-pdk"]] +version = "1.6.0" +backend = "github:extism/js-pdk" + +[tools."github:extism/js-pdk"."platforms.linux-arm64"] +checksum = "sha256:15a186250e68d6bff4ec839fff275d45a90e383a69209dcc1239eb9e3aee6e1b" +url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-aarch64-linux-v1.6.0.gz" +url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223214" + +[tools."github:extism/js-pdk"."platforms.linux-arm64-musl"] +checksum = "sha256:15a186250e68d6bff4ec839fff275d45a90e383a69209dcc1239eb9e3aee6e1b" +url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-aarch64-linux-v1.6.0.gz" +url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223214" + +[tools."github:extism/js-pdk"."platforms.linux-x64"] +checksum = "sha256:4ded271ccf465031ccd0dc35e7a140e134d7f30721671cc4a8e1ff805d4aad68" +url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-x86_64-linux-v1.6.0.gz" +url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223119" + +[tools."github:extism/js-pdk"."platforms.linux-x64-musl"] +checksum = "sha256:4ded271ccf465031ccd0dc35e7a140e134d7f30721671cc4a8e1ff805d4aad68" +url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-x86_64-linux-v1.6.0.gz" +url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223119" + +[tools."github:extism/js-pdk"."platforms.macos-arm64"] +checksum = "sha256:548e25bda3971a07c32d78a249135cf8cb7b3eede101e878e06e53e01ac2e0ce" +url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-aarch64-macos-v1.6.0.gz" +url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223215" + +[tools."github:extism/js-pdk"."platforms.macos-x64"] +checksum = "sha256:d85a875c2a071f0c29fe572764c52c3a499f157ab7f9efac8939a4364390e29b" +url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-x86_64-macos-v1.6.0.gz" +url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223239" + +[tools."github:extism/js-pdk"."platforms.windows-x64"] +checksum = "sha256:97b7b746141e4777e1ca2b76febdeb16dc9d314ff6a4257df05a476b67228acc" +url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-x86_64-windows-v1.6.0.gz" +url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353224133" + +[[tools."github:jellyfin/jellyfin-ffmpeg"]] +version = "7.1.3-6" +backend = "github:jellyfin/jellyfin-ffmpeg" + +[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-arm64"] +checksum = "sha256:bea03c670e8cc5bfe9edc0c5d624d4735421610cef5e808db93e7d8596952886" +url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linuxarm64-gpl.tar.xz" +url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048876" + +[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-arm64-musl"] +checksum = "sha256:bea03c670e8cc5bfe9edc0c5d624d4735421610cef5e808db93e7d8596952886" +url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linuxarm64-gpl.tar.xz" +url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048876" + +[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-x64"] +checksum = "sha256:39e99a7927468a6abec5f65d00f55010e8ff2ae3c2605294f179c94f6ae21af2" +url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linux64-gpl.tar.xz" +url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048879" + +[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-x64-musl"] +checksum = "sha256:39e99a7927468a6abec5f65d00f55010e8ff2ae3c2605294f179c94f6ae21af2" +url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linux64-gpl.tar.xz" +url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048879" + +[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.macos-arm64"] +checksum = "sha256:e024d5e78d5414e75f0181036cd21373fafb9270c72894dfd7dbda2572439820" +url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_macarm64-gpl.tar.xz" +url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/408995838" + +[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.macos-x64"] +checksum = "sha256:066ede9774aaae97a18098aaeea8b7e0d286653eb8618f640476e99c59a536c2" +url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_mac64-gpl.tar.xz" +url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/408995889" + +[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.windows-x64"] +checksum = "sha256:7b7168149689610296f3a187c717056ce0786cc125a31caf28056737e9ba1cc1" +url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_win64-clang-gpl.zip" +url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409036094" + +[[tools."github:webassembly/binaryen"]] +version = "version_124" +backend = "github:webassembly/binaryen" + +[tools."github:webassembly/binaryen"."platforms.linux-arm64"] +checksum = "sha256:6291bd9a57d8e046f3bc099a4db386c147433a87f71c783a901c5b1792e38de3" +url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-aarch64-linux.tar.gz" +url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288927659" + +[tools."github:webassembly/binaryen"."platforms.linux-arm64-musl"] +checksum = "sha256:6291bd9a57d8e046f3bc099a4db386c147433a87f71c783a901c5b1792e38de3" +url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-aarch64-linux.tar.gz" +url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288927659" + +[tools."github:webassembly/binaryen"."platforms.linux-x64"] +checksum = "sha256:0290c3779fedf592b8da0ded3032ff55c41a2b7bfa2d6bf7b7bac6f0e6e28963" +url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-x86_64-linux.tar.gz" +url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288926769" + +[tools."github:webassembly/binaryen"."platforms.linux-x64-musl"] +checksum = "sha256:0290c3779fedf592b8da0ded3032ff55c41a2b7bfa2d6bf7b7bac6f0e6e28963" +url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-x86_64-linux.tar.gz" +url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288926769" + +[tools."github:webassembly/binaryen"."platforms.macos-arm64"] +checksum = "sha256:86a2c960ff62c6d2ea6009d1f89745c22c70100d394a095eab45eb941bdaa24c" +url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-arm64-macos.tar.gz" +url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288926134" + +[tools."github:webassembly/binaryen"."platforms.macos-x64"] +checksum = "sha256:b389bb0731758d86c3cb266d01d28a12725c23bd3cabc3df34faa162af0887e9" +url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-x86_64-macos.tar.gz" +url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288926135" + +[tools."github:webassembly/binaryen"."platforms.windows-x64"] +checksum = "sha256:b5e1d2a1ad3c03229ddc89823848f4a1c11f9c6402a51fa26f0aaa5f1d7a2203" +url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-x86_64-windows.tar.gz" +url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288925833" + +[[tools.java]] +version = "21.0.2" +backend = "core:java" + +[tools.java."platforms.linux-arm64"] +checksum = "sha256:08db1392a48d4eb5ea5315cf8f18b89dbaf36cda663ba882cf03c704c9257ec2" +url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_linux-aarch64_bin.tar.gz" + +[tools.java."platforms.linux-x64"] +checksum = "sha256:a2def047a73941e01a73739f92755f86b895811afb1f91243db214cff5bdac3f" +url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_linux-x64_bin.tar.gz" + +[tools.java."platforms.macos-arm64"] +checksum = "sha256:b3d588e16ec1e0ef9805d8a696591bd518a5cea62567da8f53b5ce32d11d22e4" +url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_macos-aarch64_bin.tar.gz" + +[tools.java."platforms.macos-x64"] +checksum = "sha256:8fd09e15dc406387a0aba70bf5d99692874e999bf9cd9208b452b5d76ac922d3" +url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_macos-x64_bin.tar.gz" + +[tools.java."platforms.windows-x64"] +checksum = "sha256:b6c17e747ae78cdd6de4d7532b3164b277daee97c007d3eaa2b39cca99882664" +url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_windows-x64_bin.zip" + +[[tools.node]] +version = "24.15.0" +backend = "core:node" + +[tools.node."platforms.linux-arm64"] +checksum = "sha256:73afc234d558c24919875f51c2d1ea002a2ada4ea6f83601a383869fefa64eed" +url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-linux-arm64.tar.gz" + +[tools.node."platforms.linux-arm64-musl"] +checksum = "sha256:31e98aa960a067da91edffd5d93bc46657b5d2a8029612c359f5f2ac0060152a" +url = "https://unofficial-builds.nodejs.org/download/release/v24.15.0/node-v24.15.0-linux-arm64-musl.tar.gz" + +[tools.node."platforms.linux-x64"] +checksum = "sha256:44836872d9aec49f1e6b52a9a922872db9a2b02d235a616a5681b6a85fec8d89" +url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-linux-x64.tar.gz" + +[tools.node."platforms.linux-x64-musl"] +checksum = "sha256:f55af5bd489c5347b113ca6594cae00a54b30ba57ac5875324311bfc6f4762e3" +url = "https://unofficial-builds.nodejs.org/download/release/v24.15.0/node-v24.15.0-linux-x64-musl.tar.gz" + +[tools.node."platforms.macos-arm64"] +checksum = "sha256:372331b969779ab5d15b949884fc6eaf88d5afe87bde8ba881d6400b9100ffc4" +url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-darwin-arm64.tar.gz" + +[tools.node."platforms.macos-x64"] +checksum = "sha256:ffd5ee293467927f3ee731a553eb88fd1f48cf74eebc2d74a6babe4af228673b" +url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-darwin-x64.tar.gz" + +[tools.node."platforms.windows-x64"] +checksum = "sha256:cc5149eabd53779ce1e7bdc5401643622d0c7e6800ade18928a767e940bb0e62" +url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-win-x64.zip" + +[[tools."npm:oazapfts"]] +version = "7.5.0" +backend = "npm:oazapfts" + +[[tools.opentofu]] +version = "1.11.6" +backend = "aqua:opentofu/opentofu" + +[tools.opentofu."platforms.linux-arm64"] +checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850" +url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz" + +[tools.opentofu."platforms.linux-arm64-musl"] +checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850" +url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz" + +[tools.opentofu."platforms.linux-x64"] +checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e" +url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz" + +[tools.opentofu."platforms.linux-x64-musl"] +checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e" +url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz" + +[tools.opentofu."platforms.macos-arm64"] +checksum = "sha256:62d7fa8539e13b444827aa0a3b90c5972da5c47e8f8882d9dcf2e430e78840c1" +url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_arm64.tar.gz" + +[tools.opentofu."platforms.macos-x64"] +checksum = "sha256:1408cdef1c380f914565e6b4bb70794c6b163f195fcb233357f3d6c5745906b6" +url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_amd64.tar.gz" + +[tools.opentofu."platforms.windows-x64"] +checksum = "sha256:27323f70c875b8251bfd7e61a4cffc3ebff4e56ed1e611b955016f0c7077367e" +url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_windows_amd64.tar.gz" + +[[tools.pnpm]] +version = "10.33.4" +backend = "aqua:pnpm/pnpm" + +[[tools.terragrunt]] +version = "1.0.3" +backend = "aqua:gruntwork-io/terragrunt" + +[tools.terragrunt."platforms.linux-arm64"] +checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz" + +[tools.terragrunt."platforms.linux-arm64-musl"] +checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz" + +[tools.terragrunt."platforms.linux-x64"] +checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz" + +[tools.terragrunt."platforms.linux-x64-musl"] +checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz" + +[tools.terragrunt."platforms.macos-arm64"] +checksum = "sha256:aacb5be2ca5475300cbce246dfbd8a45eb47510fbaa70fab8561c49ef5db03aa" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_arm64.tar.gz" + +[tools.terragrunt."platforms.macos-x64"] +checksum = "sha256:3133c2251e191aede8e3dd2a5b3aee2e91c5f08f88f117aee40eed9a24c8ef6b" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_amd64.tar.gz" + +[tools.terragrunt."platforms.windows-x64"] +checksum = "sha256:183b2745b4e04980a6bfa4450ff81956a12596ca22d70f7aaa793980f5b036db" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_windows_amd64.exe.tar.gz" diff --git a/mise.toml b/mise.toml index f190490f17..131067e92e 100644 --- a/mise.toml +++ b/mise.toml @@ -2,7 +2,7 @@ experimental_monorepo_root = true [monorepo] config_roots = [ - "packages/plugins", + "packages/plugin-core", "server", "packages/cli", "deployment", @@ -16,18 +16,28 @@ config_roots = [ [tools] node = "24.15.0" -flutter = "3.41.9" -pnpm = "10.33.1" +"aqua:flutter/flutter" = "3.41.9" +pnpm = "10.33.4" terragrunt = "1.0.3" opentofu = "1.11.6" java = "21.0.2" "npm:oazapfts" = "7.5.0" +"github:extism/cli" = "1.6.3" +"github:webassembly/binaryen" = "version_124" +"github:extism/js-pdk" = "1.6.0" [tools."github:CQLabs/homebrew-dcm"] version = "1.37.0" bin = "dcm" postinstall = "chmod +x \"$MISE_TOOL_INSTALL_PATH/dcm\" || true" +[tools."github:CQLabs/homebrew-dcm".platforms] +linux-x64 = { asset_pattern = "dcm-linux-x64-release.zip" } +linux-arm64 = { asset_pattern = "dcm-linux-arm-release.zip" } +macos-x64 = { asset_pattern = "dcm-macos-x64-release.zip" } +macos-arm64 = { asset_pattern = "dcm-macos-arm-release.zip" } +windows-x64 = { asset_pattern = "dcm-windows-release.zip" } + [tools."github:jellyfin/jellyfin-ffmpeg"] version = "7.1.3-6" @@ -40,6 +50,13 @@ macos-arm64 = { asset_pattern = "jellyfin-ffmpeg_*_portable_macarm64-gpl.tar.xz" [settings] experimental = true pin = true +lockfile = true + +[tasks.plugins] +run = [ + "pnpm --filter @immich/plugin-sdk --filter @immich/plugin-core install --frozen-lockfile", + "pnpm --filter @immich/plugin-sdk --filter @immich/plugin-core build", +] [tasks.open-api-typescript] run = [ @@ -55,11 +72,13 @@ run = "bash ./bin/generate-dart-sdk.sh" [tasks.open-api] env = { SHARP_IGNORE_GLOBAL_LIBVIPS = true } run = [ + { task = "//:plugins" }, + { task = "//server:build" }, { task = "//server:install" }, { task = "//server:build" }, { task = "//server:sync-open-api" }, - { task = ":open-api-typescript"}, - { task = ":open-api-dart"}, + { task = ":open-api-typescript" }, + { task = ":open-api-dart" }, ] [tasks.sql] diff --git a/mobile/android/app/build.gradle b/mobile/android/app/build.gradle index 7e3d67fa81..1f22eeaf52 100644 --- a/mobile/android/app/build.gradle +++ b/mobile/android/app/build.gradle @@ -89,6 +89,13 @@ flutter { } dependencies { + constraints { + implementation("androidx.glance:glance-appwidget") { + version { strictly libs.versions.glance.get() } + because 'home_widget requests 1.+ which can resolve to pre-releases incompatible with our compileSdk/AGP' + } + } + implementation libs.okhttp implementation libs.cronet.embedded implementation libs.media3.datasource.okhttp diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/MainActivity.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/MainActivity.kt index 9205697a53..fc9ab28fa2 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/MainActivity.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/MainActivity.kt @@ -18,6 +18,8 @@ import app.alextran.immich.images.LocalImageApi import app.alextran.immich.images.LocalImagesImpl import app.alextran.immich.images.RemoteImageApi import app.alextran.immich.images.RemoteImagesImpl +import app.alextran.immich.permission.PermissionApi +import app.alextran.immich.permission.PermissionApiImpl import app.alextran.immich.sync.NativeSyncApi import app.alextran.immich.sync.NativeSyncApiImpl26 import app.alextran.immich.sync.NativeSyncApiImpl30 @@ -51,7 +53,9 @@ class MainActivity : FlutterFragmentActivity() { } else { NativeSyncApiImpl30(ctx) } + val permissionApiImpl = PermissionApiImpl(ctx) NativeSyncApi.setUp(messenger, nativeSyncApiImpl) + PermissionApi.setUp(messenger, permissionApiImpl) LocalImageApi.setUp(messenger, LocalImagesImpl(ctx)) RemoteImageApi.setUp(messenger, RemoteImagesImpl(ctx)) @@ -61,6 +65,7 @@ class MainActivity : FlutterFragmentActivity() { flutterEngine.plugins.add(ViewIntentPlugin()) flutterEngine.plugins.add(backgroundEngineLockImpl) flutterEngine.plugins.add(nativeSyncApiImpl) + flutterEngine.plugins.add(permissionApiImpl) } fun cancelPlugins(flutterEngine: FlutterEngine) { @@ -68,6 +73,8 @@ class MainActivity : FlutterFragmentActivity() { flutterEngine.plugins.get(NativeSyncApiImpl26::class.java) as ImmichPlugin? ?: flutterEngine.plugins.get(NativeSyncApiImpl30::class.java) as ImmichPlugin? nativeApi?.detachFromEngine() + val permissionApi = flutterEngine.plugins.get(PermissionApiImpl::class.java) as ImmichPlugin? + permissionApi?.detachFromEngine() } } } diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/core/Network.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/core/Network.g.kt index 1687a7ba95..c380a0a6a5 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/core/Network.g.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/core/Network.g.kt @@ -315,6 +315,7 @@ interface NetworkApi { fun hasCertificate(): Boolean fun getClientPointer(): Long fun setRequestHeaders(headers: Map, serverUrls: List, token: String?) + fun getAppGroupId(): String companion object { /** The codec used by NetworkApi. */ @@ -430,6 +431,21 @@ interface NetworkApi { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NetworkApi.getAppGroupId$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + listOf(api.getAppGroupId()) + } catch (exception: Throwable) { + NetworkPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } } } } diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/core/NetworkApiPlugin.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/core/NetworkApiPlugin.kt index 85b7a6c730..4479ec7701 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/core/NetworkApiPlugin.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/core/NetworkApiPlugin.kt @@ -13,7 +13,7 @@ class NetworkApiPlugin : FlutterPlugin, ActivityAware { private var networkApi: NetworkApiImpl? = null override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { - networkApi = NetworkApiImpl() + networkApi = NetworkApiImpl(binding.applicationContext) NetworkApi.setUp(binding.binaryMessenger, networkApi) } @@ -39,9 +39,11 @@ class NetworkApiPlugin : FlutterPlugin, ActivityAware { } } -private class NetworkApiImpl : NetworkApi { +private class NetworkApiImpl(private val context: Context) : NetworkApi { var activity: Activity? = null + override fun getAppGroupId(): String = context.packageName + override fun addCertificate(clientData: ClientCertData, callback: (Result) -> Unit) { try { HttpClientManager.setKeyEntry(clientData.data, clientData.password.toCharArray()) diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/images/RemoteImagesImpl.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/images/RemoteImagesImpl.kt index 9255eff44b..f7ebc349f6 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/images/RemoteImagesImpl.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/images/RemoteImagesImpl.kt @@ -23,6 +23,8 @@ import java.io.IOException import java.nio.ByteBuffer import java.util.concurrent.ConcurrentHashMap +private const val MAX_PREALLOC_BYTES = 128 * 1024 * 1024 + private class RemoteRequest(val cancellationSignal: CancellationSignal) class RemoteImagesImpl(context: Context) : RemoteImageApi { @@ -228,7 +230,6 @@ private class CronetImageFetcher : ImageFetcher { private val onComplete: () -> Unit, ) : UrlRequest.Callback() { private var buffer: NativeByteBuffer? = null - private var wrapped: ByteBuffer? = null private var error: Exception? = null override fun onRedirectReceived(request: UrlRequest, info: UrlResponseInfo, newUrl: String) { @@ -242,15 +243,16 @@ private class CronetImageFetcher : ImageFetcher { } try { + // Content-Length is a size hint only. With Content-Encoding (gzip/br/...), + // Cronet auto-decompresses and writes decompressed bytes to our buffer, which + // may exceed the wire/compressed Content-Length. Always use the growable + // buffer path so we can't overflow. val contentLength = info.allHeaders["content-length"]?.firstOrNull()?.toIntOrNull() ?: 0 - if (contentLength > 0) { - buffer = NativeByteBuffer(contentLength + 1) - wrapped = NativeBuffer.wrap(buffer!!.pointer, contentLength + 1) - request.read(wrapped) - } else { - buffer = NativeByteBuffer(INITIAL_BUFFER_SIZE) - request.read(buffer!!.wrapRemaining()) - } + // Cap the up-front alloc: Content-Length is untrusted and can be huge or near + // Int.MAX_VALUE (overflowing `+1`). For larger responses the grow path takes over. + val initialSize = if (contentLength in 1..MAX_PREALLOC_BYTES) contentLength + 1 else INITIAL_BUFFER_SIZE + buffer = NativeByteBuffer(initialSize) + request.read(buffer!!.wrapRemaining()) } catch (e: Exception) { error = e return request.cancel() @@ -263,14 +265,14 @@ private class CronetImageFetcher : ImageFetcher { byteBuffer: ByteBuffer ) { try { - val buf = if (wrapped == null) { - buffer!!.run { - advance(byteBuffer.position()) - ensureHeadroom() - wrapRemaining() - } - } else { - wrapped + // Always pass a fresh wrap so byteBuffer.position() represents only the + // bytes Cronet wrote in this iteration. Reusing the caller-supplied + // ByteBuffer breaks advance(): Cronet's position keeps accumulating + // across reads, which would double-count previous iterations' bytes. + val buf = buffer!!.run { + advance(byteBuffer.position()) + ensureHeadroom() + wrapRemaining() } request.read(buf) } catch (e: Exception) { @@ -280,7 +282,6 @@ private class CronetImageFetcher : ImageFetcher { } override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo) { - wrapped?.let { buffer!!.advance(it.position()) } onSuccess(buffer!!) onComplete() } diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/ManageMediaPermissionDelegate.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/ManageMediaPermissionDelegate.kt new file mode 100644 index 0000000000..ddabfbabd8 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/ManageMediaPermissionDelegate.kt @@ -0,0 +1,96 @@ +package app.alextran.immich.permission + +import android.content.Context +import android.content.Intent +import android.os.Build +import android.provider.MediaStore +import android.provider.Settings +import androidx.core.net.toUri +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding +import io.flutter.plugin.common.PluginRegistry + +class ManageMediaPermissionDelegate( + context: Context, + private val requestCode: Int = 1003, +) : PluginRegistry.ActivityResultListener { + private val ctx = context.applicationContext + private var activityBinding: ActivityPluginBinding? = null + private var pendingResult: ((Result) -> Unit)? = null + + fun hasManageMediaPermission(): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + MediaStore.canManageMedia(ctx) + } else { + false + } + } + + fun requestManageMediaPermission(callback: (Result) -> Unit) { + if (hasManageMediaPermission()) { + callback(Result.success(true)) + return + } + + openManageMediaPermissionSettings(callback) + } + + fun manageMediaPermission(callback: (Result) -> Unit) { + openManageMediaPermissionSettings(callback) + } + + private fun openManageMediaPermissionSettings(callback: (Result) -> Unit) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { + callback(Result.success(false)) + return + } + + val activity = activityBinding?.activity + if (activity == null) { + callback(Result.failure(FlutterError("NO_ACTIVITY", "Activity not available", null))) + return + } + + pendingResult = callback + val intent = Intent(Settings.ACTION_REQUEST_MANAGE_MEDIA).apply { + data = "package:${activity.packageName}".toUri() + } + try { + activity.startActivityForResult(intent, requestCode) + } catch (e: Exception) { + pendingResult = null + callback( + Result.failure( + FlutterError("ACTIVITY_LAUNCH_FAILED", "Failed to launch MANAGE_MEDIA settings", e.toString()) + ) + ) + } + } + + fun onAttachedToActivity(binding: ActivityPluginBinding) { + activityBinding = binding + binding.addActivityResultListener(this) + } + + fun onDetachedFromActivity() { + failPending("ACTIVITY_DETACHED", "Activity detached before MANAGE_MEDIA result") + activityBinding?.removeActivityResultListener(this) + activityBinding = null + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { + if (requestCode == this.requestCode) { + val callback = pendingResult + pendingResult = null + callback?.invoke(Result.success(hasManageMediaPermission())) + return true + } + + return false + } + + private fun failPending(code: String, message: String) { + val callback = pendingResult ?: return + pendingResult = null + callback(Result.failure(FlutterError(code, message, null))) + } +} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApi.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApi.g.kt new file mode 100644 index 0000000000..48a1a72037 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApi.g.kt @@ -0,0 +1,128 @@ +// Autogenerated from Pigeon (v26.3.4), do not edit directly. +// See also: https://pub.dev/packages/pigeon +@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") + +package app.alextran.immich.permission + +import android.util.Log +import io.flutter.plugin.common.BasicMessageChannel +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.MessageCodec +import io.flutter.plugin.common.StandardMethodCodec +import io.flutter.plugin.common.StandardMessageCodec +import java.io.ByteArrayOutputStream +import java.nio.ByteBuffer +private object PermissionApiPigeonUtils { + + fun wrapResult(result: Any?): List { + return listOf(result) + } + + fun wrapError(exception: Throwable): List { + return if (exception is FlutterError) { + listOf( + exception.code, + exception.message, + exception.details + ) + } else { + listOf( + exception.javaClass.simpleName, + exception.toString(), + "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception) + ) + } + } +} + +/** + * Error class for passing custom error details to Flutter via a thrown PlatformException. + * @property code The error code. + * @property message The error message. + * @property details The error details. Must be a datatype supported by the api codec. + */ +class FlutterError ( + val code: String, + override val message: String? = null, + val details: Any? = null +) : RuntimeException() +private open class PermissionApiPigeonCodec : StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return super.readValueOfType(type, buffer) + } + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + super.writeValue(stream, value) + } +} + + +/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ +interface PermissionApi { + fun hasManageMediaPermission(): Boolean + fun requestManageMediaPermission(callback: (Result) -> Unit) + fun manageMediaPermission(callback: (Result) -> Unit) + + companion object { + /** The codec used by PermissionApi. */ + val codec: MessageCodec by lazy { + PermissionApiPigeonCodec() + } + /** Sets up an instance of `PermissionApi` to handle messages through the `binaryMessenger`. */ + @JvmOverloads + fun setUp(binaryMessenger: BinaryMessenger, api: PermissionApi?, messageChannelSuffix: String = "") { + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.PermissionApi.hasManageMediaPermission$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + listOf(api.hasManageMediaPermission()) + } catch (exception: Throwable) { + PermissionApiPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.PermissionApi.requestManageMediaPermission$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + api.requestManageMediaPermission{ result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(PermissionApiPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(PermissionApiPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.PermissionApi.manageMediaPermission$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + api.manageMediaPermission{ result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(PermissionApiPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(PermissionApiPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + } + } +} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApiImpl.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApiImpl.kt new file mode 100644 index 0000000000..c3443bb06d --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApiImpl.kt @@ -0,0 +1,37 @@ +package app.alextran.immich.permission + +import android.content.Context +import app.alextran.immich.core.ImmichPlugin +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding + +class PermissionApiImpl(context: Context) : ImmichPlugin(), PermissionApi, ActivityAware { + private val manageMediaPermissionDelegate = ManageMediaPermissionDelegate(context) + + override fun hasManageMediaPermission(): Boolean = + manageMediaPermissionDelegate.hasManageMediaPermission() + + override fun requestManageMediaPermission(callback: (Result) -> Unit) { + manageMediaPermissionDelegate.requestManageMediaPermission { completeWhenActive(callback, it) } + } + + override fun manageMediaPermission(callback: (Result) -> Unit) { + manageMediaPermissionDelegate.manageMediaPermission { completeWhenActive(callback, it) } + } + + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + manageMediaPermissionDelegate.onAttachedToActivity(binding) + } + + override fun onDetachedFromActivityForConfigChanges() { + manageMediaPermissionDelegate.onDetachedFromActivity() + } + + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { + manageMediaPermissionDelegate.onAttachedToActivity(binding) + } + + override fun onDetachedFromActivity() { + manageMediaPermissionDelegate.onDetachedFromActivity() + } +} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MediaTrashDelegate.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MediaTrashDelegate.kt new file mode 100644 index 0000000000..fbf4d7e919 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MediaTrashDelegate.kt @@ -0,0 +1,133 @@ +package app.alextran.immich.sync + +import android.app.Activity +import android.content.ContentResolver +import android.content.ContentUris +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.provider.MediaStore +import androidx.annotation.RequiresApi +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding +import io.flutter.plugin.common.PluginRegistry + +class MediaTrashDelegate( + context: Context, + private val trashRequestCode: Int = 1002, +) : PluginRegistry.ActivityResultListener { + private val ctx = context.applicationContext + private var activityBinding: ActivityPluginBinding? = null + private var pendingResult: ((Result) -> Unit)? = null + + private fun hasManageMediaPermission(): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + MediaStore.canManageMedia(ctx) + } else { + false + } + } + + fun restoreFromTrashById(mediaId: String, type: Long, callback: (Result) -> Unit) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R || !hasManageMediaPermission()) { + callback(Result.failure(FlutterError("PERMISSION_DENIED", "Media permission required", null))) + return + } + + val id = mediaId.toLongOrNull() + if (id == null) { + callback(Result.failure(FlutterError("INVALID_ID", "The file id is not a valid number: $mediaId", null))) + return + } + + if (!isInTrash(id)) { + callback(Result.failure(FlutterError("TRASH_NOT_FOUND", "Item with id=$id not found in trash", null))) + return + } + + restoreUri(ContentUris.withAppendedId(contentUriForType(type.toInt()), id), callback) + } + + @RequiresApi(Build.VERSION_CODES.R) + private fun restoreUri( + contentUri: Uri, + callback: (Result) -> Unit, + ) { + val activity = activityBinding?.activity + if (activity == null) { + callback(Result.failure(FlutterError("NO_ACTIVITY", "Activity not available", null))) + return + } + + try { + val pendingIntent = MediaStore.createTrashRequest(ctx.contentResolver, listOf(contentUri), false) + pendingResult = callback + activity.startIntentSenderForResult( + pendingIntent.intentSender, + trashRequestCode, + null, + 0, + 0, + 0, + ) + } catch (e: Exception) { + pendingResult = null + callback( + Result.failure( + FlutterError("TRASH_ERROR", "Error creating or starting trash request", e.toString()) + ) + ) + } + } + + @RequiresApi(Build.VERSION_CODES.R) + private fun isInTrash(id: Long): Boolean { + val filesUri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL) + val args = Bundle().apply { + putString(ContentResolver.QUERY_ARG_SQL_SELECTION, "${MediaStore.Files.FileColumns._ID}=?") + putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, arrayOf(id.toString())) + putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_ONLY) + putInt(ContentResolver.QUERY_ARG_LIMIT, 1) + } + return ctx.contentResolver.query(filesUri, arrayOf(MediaStore.Files.FileColumns._ID), args, null) + ?.use { it.moveToFirst() } == true + } + + private fun contentUriForType(type: Int): Uri = + when (type) { + // Same order as AssetType from Dart. + 1 -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI + 2 -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI + 3 -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + else -> MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL) + } + + fun onAttachedToActivity(binding: ActivityPluginBinding) { + activityBinding = binding + binding.addActivityResultListener(this) + } + + fun onDetachedFromActivity() { + failPending("ACTIVITY_DETACHED", "Activity detached before trash result") + activityBinding?.removeActivityResultListener(this) + activityBinding = null + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { + if (requestCode == trashRequestCode) { + val callback = pendingResult + pendingResult = null + callback?.invoke(Result.success(resultCode == Activity.RESULT_OK)) + return true + } + + return false + } + + private fun failPending(code: String, message: String) { + val callback = pendingResult ?: return + pendingResult = null + callback(Result.failure(FlutterError(code, message, null))) + } +} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt index fb3aab19e8..1daa1fbaa7 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt @@ -554,6 +554,7 @@ interface NativeSyncApi { fun hashFiles(paths: List, callback: (Result>) -> Unit) fun cancelHashing() fun getTrashedAssets(): Map> + fun restoreFromTrashById(mediaId: String, type: Long, callback: (Result) -> Unit) fun getCloudIdForAssetIds(assetIds: List): List companion object { @@ -768,6 +769,27 @@ interface NativeSyncApi { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.restoreFromTrashById$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val mediaIdArg = args[0] as String + val typeArg = args[1] as Long + api.restoreFromTrashById(mediaIdArg, typeArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(MessagesPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } run { val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds$separatedMessageChannelSuffix", codec, taskQueue) if (api != null) { diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt index 42af795db3..bc2138546e 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt @@ -17,6 +17,8 @@ import com.bumptech.glide.Glide import com.bumptech.glide.load.ImageHeaderParser import com.bumptech.glide.load.ImageHeaderParserUtils import com.bumptech.glide.load.resource.bitmap.DefaultImageHeaderParser +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -41,10 +43,11 @@ sealed class AssetResult { private const val TAG = "NativeSyncApiImplBase" @SuppressLint("InlinedApi") -open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() { +open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAware { private val ctx: Context = context.applicationContext private var hashTask: Job? = null + private val mediaTrashDelegate = MediaTrashDelegate(ctx) companion object { private const val MAX_CONCURRENT_HASH_OPERATIONS = 16 @@ -510,6 +513,26 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() { hashTask = null } + fun restoreFromTrashById(mediaId: String, type: Long, callback: (Result) -> Unit) { + mediaTrashDelegate.restoreFromTrashById(mediaId, type) { completeWhenActive(callback, it) } + } + + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + mediaTrashDelegate.onAttachedToActivity(binding) + } + + override fun onDetachedFromActivityForConfigChanges() { + mediaTrashDelegate.onDetachedFromActivity() + } + + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { + mediaTrashDelegate.onAttachedToActivity(binding) + } + + override fun onDetachedFromActivity() { + mediaTrashDelegate.onDetachedFromActivity() + } + // This method is only implemented on iOS; on Android, we do not have a concept of cloud IDs @Suppress("unused", "UNUSED_PARAMETER") fun getCloudIdForAssetIds(assetIds: List): List { diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj index fbffa69ba5..65cc5ec433 100644 --- a/mobile/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -19,6 +19,8 @@ B21E34AC2E5B09190031FDB9 /* BackgroundWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B21E34AB2E5B09100031FDB9 /* BackgroundWorker.swift */; }; B25D377A2E72CA15008B6CA7 /* Connectivity.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25D37782E72CA15008B6CA7 /* Connectivity.g.swift */; }; B25D377C2E72CA26008B6CA7 /* ConnectivityApiImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25D377B2E72CA20008B6CA7 /* ConnectivityApiImpl.swift */; }; + B2EE00022E72CA15008B6CA7 /* PermissionApi.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2EE00012E72CA15008B6CA7 /* PermissionApi.g.swift */; }; + B2EE00042E72CA15008B6CA7 /* PermissionApiImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2EE00032E72CA15008B6CA7 /* PermissionApiImpl.swift */; }; B2BE315F2E5E5229006EEF88 /* BackgroundWorker.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2BE315E2E5E5229006EEF88 /* BackgroundWorker.g.swift */; }; D218389C4A4C4693F141F7D1 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 886774DBDDE6B35BF2B4F2CD /* Pods_Runner.framework */; }; F02538E92DFBCBDD008C3FA3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; @@ -105,6 +107,8 @@ B21E34AB2E5B09100031FDB9 /* BackgroundWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundWorker.swift; sourceTree = ""; }; B25D37782E72CA15008B6CA7 /* Connectivity.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Connectivity.g.swift; sourceTree = ""; }; B25D377B2E72CA20008B6CA7 /* ConnectivityApiImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectivityApiImpl.swift; sourceTree = ""; }; + B2EE00012E72CA15008B6CA7 /* PermissionApi.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionApi.g.swift; sourceTree = ""; }; + B2EE00032E72CA15008B6CA7 /* PermissionApiImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionApiImpl.swift; sourceTree = ""; }; B2BE315E2E5E5229006EEF88 /* BackgroundWorker.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundWorker.g.swift; sourceTree = ""; }; E0E99CDC17B3EB7FA8BA2332 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; F0B57D382DF764BD00DC5BCC /* WidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -283,6 +287,7 @@ B25D37792E72CA15008B6CA7 /* Connectivity */, B21E34A62E5AF9760031FDB9 /* Background */, B2CF7F8C2DDE4EBB00744BF6 /* Sync */, + B2EE00052E72CA15008B6CA7 /* Permission */, FA9973382CF6DF4B000EF859 /* Runner.entitlements */, FAC7416727DB9F5500C668D8 /* RunnerProfile.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, @@ -317,6 +322,15 @@ path = Connectivity; sourceTree = ""; }; + B2EE00052E72CA15008B6CA7 /* Permission */ = { + isa = PBXGroup; + children = ( + B2EE00032E72CA15008B6CA7 /* PermissionApiImpl.swift */, + B2EE00012E72CA15008B6CA7 /* PermissionApi.g.swift */, + ); + path = Permission; + sourceTree = ""; + }; FAC6F8B62D287F120078CB2F /* ShareExtension */ = { isa = PBXGroup; children = ( @@ -619,6 +633,8 @@ FE5499F42F1197D8006016CB /* RemoteImages.g.swift in Sources */, FE5FE4AE2F30FBC000A71243 /* ImageProcessing.swift in Sources */, B25D377A2E72CA15008B6CA7 /* Connectivity.g.swift in Sources */, + B2EE00022E72CA15008B6CA7 /* PermissionApi.g.swift in Sources */, + B2EE00042E72CA15008B6CA7 /* PermissionApiImpl.swift in Sources */, FE5499F82F1198E2006016CB /* RemoteImagesImpl.swift in Sources */, FEAFA8732E4D42F4001E47FE /* Thumbhash.swift in Sources */, B25D377C2E72CA26008B6CA7 /* ConnectivityApiImpl.swift in Sources */, @@ -718,6 +734,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; + CUSTOM_GROUP_ID = group.app.immich.share.profile; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -750,7 +767,6 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 240; - CUSTOM_GROUP_ID = group.app.immich.share; DEVELOPMENT_TEAM = 2W7AC6T8T5; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -801,6 +817,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; + CUSTOM_GROUP_ID = group.app.immich.share.debug; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -860,6 +877,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; + CUSTOM_GROUP_ID = group.app.immich.share; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -894,7 +912,6 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 240; - CUSTOM_GROUP_ID = group.app.immich.share; DEVELOPMENT_TEAM = 2W7AC6T8T5; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -924,7 +941,6 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 240; - CUSTOM_GROUP_ID = group.app.immich.share; DEVELOPMENT_TEAM = 2W7AC6T8T5; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -1080,7 +1096,6 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 240; - CUSTOM_GROUP_ID = group.app.immich.share; DEVELOPMENT_TEAM = 2W7AC6T8T5; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -1124,7 +1139,6 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 240; - CUSTOM_GROUP_ID = group.app.immich.share; DEVELOPMENT_TEAM = 2W7AC6T8T5; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -1165,7 +1179,6 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 240; - CUSTOM_GROUP_ID = group.app.immich.share; DEVELOPMENT_TEAM = 2W7AC6T8T5; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; diff --git a/mobile/ios/Runner/AppDelegate.swift b/mobile/ios/Runner/AppDelegate.swift index 216146a6f3..e1ff3b1f6e 100644 --- a/mobile/ios/Runner/AppDelegate.swift +++ b/mobile/ios/Runner/AppDelegate.swift @@ -26,6 +26,7 @@ import native_video_player public static func registerPlugins(with registry: FlutterPluginRegistry, messenger: FlutterBinaryMessenger) { NativeSyncApiImpl.register(with: registry.registrar(forPlugin: NativeSyncApiImpl.name)!) + PermissionApiSetup.setUp(binaryMessenger: messenger, api: PermissionApiImpl()) LocalImageApiSetup.setUp(binaryMessenger: messenger, api: LocalImageApiImpl()) RemoteImageApiSetup.setUp(binaryMessenger: messenger, api: RemoteImageApiImpl()) BackgroundWorkerFgHostApiSetup.setUp(binaryMessenger: messenger, api: BackgroundWorkerApiImpl()) diff --git a/mobile/ios/Runner/Core/Network.g.swift b/mobile/ios/Runner/Core/Network.g.swift index 7d9b9f14be..265923d165 100644 --- a/mobile/ios/Runner/Core/Network.g.swift +++ b/mobile/ios/Runner/Core/Network.g.swift @@ -288,6 +288,7 @@ protocol NetworkApi { func hasCertificate() throws -> Bool func getClientPointer() throws -> Int64 func setRequestHeaders(headers: [String: String], serverUrls: [String], token: String?) throws + func getAppGroupId() throws -> String } /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. @@ -388,5 +389,18 @@ class NetworkApiSetup { } else { setRequestHeadersChannel.setMessageHandler(nil) } + let getAppGroupIdChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NetworkApi.getAppGroupId\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getAppGroupIdChannel.setMessageHandler { _, reply in + do { + let result = try api.getAppGroupId() + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getAppGroupIdChannel.setMessageHandler(nil) + } } } diff --git a/mobile/ios/Runner/Core/NetworkApiImpl.swift b/mobile/ios/Runner/Core/NetworkApiImpl.swift index 82a913d837..288b8e9539 100644 --- a/mobile/ios/Runner/Core/NetworkApiImpl.swift +++ b/mobile/ios/Runner/Core/NetworkApiImpl.swift @@ -61,6 +61,10 @@ class NetworkApiImpl: NetworkApi { return Int64(Int(bitPattern: pointer)) } + func getAppGroupId() throws -> String { + return Bundle.main.object(forInfoDictionaryKey: "AppGroupId") as! String + } + func setRequestHeaders(headers: [String : String], serverUrls: [String], token: String?) throws { URLSessionManager.setServerUrls(serverUrls) diff --git a/mobile/ios/Runner/Core/URLSessionManager.swift b/mobile/ios/Runner/Core/URLSessionManager.swift index e9d65d3113..48963aa577 100644 --- a/mobile/ios/Runner/Core/URLSessionManager.swift +++ b/mobile/ios/Runner/Core/URLSessionManager.swift @@ -4,7 +4,7 @@ import native_video_player let CLIENT_CERT_LABEL = "app.alextran.immich.client_identity" let HEADERS_KEY = "immich.request_headers" let SERVER_URLS_KEY = "immich.server_urls" -let APP_GROUP = "group.app.immich.share" +let APP_GROUP = Bundle.main.object(forInfoDictionaryKey: "AppGroupId") as! String let COOKIE_EXPIRY_DAYS: TimeInterval = 400 enum AuthCookie: CaseIterable { diff --git a/mobile/ios/Runner/Permission/PermissionApi.g.swift b/mobile/ios/Runner/Permission/PermissionApi.g.swift new file mode 100644 index 0000000000..53ad9e5b11 --- /dev/null +++ b/mobile/ios/Runner/Permission/PermissionApi.g.swift @@ -0,0 +1,106 @@ +// Autogenerated from Pigeon (v26.3.4), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +import Foundation + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#else + #error("Unsupported platform.") +#endif + +private func wrapResult(_ result: Any?) -> [Any?] { + return [result] +} + +private func wrapError(_ error: Any) -> [Any?] { + if let pigeonError = error as? PigeonError { + return [ + pigeonError.code, + pigeonError.message, + pigeonError.details, + ] + } + if let flutterError = error as? FlutterError { + return [ + flutterError.code, + flutterError.message, + flutterError.details, + ] + } + return [ + "\(error)", + "\(Swift.type(of: error))", + "Stacktrace: \(Thread.callStackSymbols)", + ] +} + +private func isNullish(_ value: Any?) -> Bool { + return value is NSNull || value == nil +} + +private func nilOrValue(_ value: Any?) -> T? { + if value is NSNull { return nil } + return value as! T? +} + +/// Generated protocol from Pigeon that represents a handler of messages from Flutter. +protocol PermissionApi { + func hasManageMediaPermission() throws -> Bool + func requestManageMediaPermission(completion: @escaping (Result) -> Void) + func manageMediaPermission(completion: @escaping (Result) -> Void) +} + +/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. +class PermissionApiSetup { + static var codec: FlutterStandardMessageCodec { FlutterStandardMessageCodec.sharedInstance() } + /// Sets up an instance of `PermissionApi` to handle messages through the `binaryMessenger`. + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: PermissionApi?, messageChannelSuffix: String = "") { + let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" + let hasManageMediaPermissionChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.PermissionApi.hasManageMediaPermission\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + hasManageMediaPermissionChannel.setMessageHandler { _, reply in + do { + let result = try api.hasManageMediaPermission() + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + hasManageMediaPermissionChannel.setMessageHandler(nil) + } + let requestManageMediaPermissionChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.PermissionApi.requestManageMediaPermission\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + requestManageMediaPermissionChannel.setMessageHandler { _, reply in + api.requestManageMediaPermission { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + requestManageMediaPermissionChannel.setMessageHandler(nil) + } + let manageMediaPermissionChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.PermissionApi.manageMediaPermission\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + manageMediaPermissionChannel.setMessageHandler { _, reply in + api.manageMediaPermission { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + manageMediaPermissionChannel.setMessageHandler(nil) + } + } +} diff --git a/mobile/ios/Runner/Permission/PermissionApiImpl.swift b/mobile/ios/Runner/Permission/PermissionApiImpl.swift new file mode 100644 index 0000000000..e725b742fd --- /dev/null +++ b/mobile/ios/Runner/Permission/PermissionApiImpl.swift @@ -0,0 +1,15 @@ +import Foundation + +class PermissionApiImpl: PermissionApi { + func hasManageMediaPermission() throws -> Bool { + return false + } + + func requestManageMediaPermission(completion: @escaping (Result) -> Void) { + completion(.success(false)) + } + + func manageMediaPermission(completion: @escaping (Result) -> Void) { + completion(.success(false)) + } +} diff --git a/mobile/ios/Runner/Runner.entitlements b/mobile/ios/Runner/Runner.entitlements index e5862cb213..c8a6be9cbb 100644 --- a/mobile/ios/Runner/Runner.entitlements +++ b/mobile/ios/Runner/Runner.entitlements @@ -10,7 +10,7 @@ com.apple.security.application-groups - group.app.immich.share + $(CUSTOM_GROUP_ID) diff --git a/mobile/ios/Runner/RunnerProfile.entitlements b/mobile/ios/Runner/RunnerProfile.entitlements index 6a5c086baf..93a4aab552 100644 --- a/mobile/ios/Runner/RunnerProfile.entitlements +++ b/mobile/ios/Runner/RunnerProfile.entitlements @@ -12,7 +12,7 @@ com.apple.security.application-groups - group.app.immich.share + $(CUSTOM_GROUP_ID) diff --git a/mobile/ios/Runner/Sync/Messages.g.swift b/mobile/ios/Runner/Sync/Messages.g.swift index 9ddc41a156..0573936625 100644 --- a/mobile/ios/Runner/Sync/Messages.g.swift +++ b/mobile/ios/Runner/Sync/Messages.g.swift @@ -538,6 +538,7 @@ protocol NativeSyncApi { func hashFiles(paths: [String], completion: @escaping (Result<[HashResult], Error>) -> Void) func cancelHashing() throws func getTrashedAssets() throws -> [String: [PlatformAsset]] + func restoreFromTrashById(mediaId: String, type: Int64, completion: @escaping (Result) -> Void) func getCloudIdForAssetIds(assetIds: [String]) throws -> [CloudIdResult] } @@ -741,6 +742,24 @@ class NativeSyncApiSetup { } else { getTrashedAssetsChannel.setMessageHandler(nil) } + let restoreFromTrashByIdChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.restoreFromTrashById\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + restoreFromTrashByIdChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let mediaIdArg = args[0] as! String + let typeArg = args[1] as! Int64 + api.restoreFromTrashById(mediaId: mediaIdArg, type: typeArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + restoreFromTrashByIdChannel.setMessageHandler(nil) + } let getCloudIdForAssetIdsChannel = taskQueue == nil ? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) : FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue) diff --git a/mobile/ios/Runner/Sync/MessagesImpl.swift b/mobile/ios/Runner/Sync/MessagesImpl.swift index f318aeba4d..249abce6c9 100644 --- a/mobile/ios/Runner/Sync/MessagesImpl.swift +++ b/mobile/ios/Runner/Sync/MessagesImpl.swift @@ -110,7 +110,7 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { var domainAlbum = PlatformAlbum( id: album.localIdentifier, - name: album.localizedTitle!, + name: album.localizedTitle ?? album.localIdentifier, updatedAt: nil, isCloud: isCloud, assetCount: Int64(assets.count) @@ -389,6 +389,10 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { func getTrashedAssets() throws -> [String: [PlatformAsset]] { throw PigeonError(code: "UNSUPPORTED_OS", message: "This feature not supported on iOS.", details: nil) } + + func restoreFromTrashById(mediaId: String, type: Int64, completion: @escaping (Result) -> Void) { + completion(.success(false)) + } private func getAssetsFromAlbum(in album: PHAssetCollection, options: PHFetchOptions) -> PHFetchResult { // Ensure to actually getting all assets for the Recents album diff --git a/mobile/ios/ShareExtension/ShareExtension.entitlements b/mobile/ios/ShareExtension/ShareExtension.entitlements index d16dcca065..1328658832 100644 --- a/mobile/ios/ShareExtension/ShareExtension.entitlements +++ b/mobile/ios/ShareExtension/ShareExtension.entitlements @@ -4,7 +4,7 @@ com.apple.security.application-groups - group.app.immich.share + $(CUSTOM_GROUP_ID) \ No newline at end of file diff --git a/mobile/ios/WidgetExtension/ImmichAPI.swift b/mobile/ios/WidgetExtension/ImmichAPI.swift index 6ae2d502f8..c5d7971aba 100644 --- a/mobile/ios/WidgetExtension/ImmichAPI.swift +++ b/mobile/ios/WidgetExtension/ImmichAPI.swift @@ -2,7 +2,7 @@ import Foundation import SwiftUI import WidgetKit -let IMMICH_SHARE_GROUP = "group.app.immich.share" +let IMMICH_SHARE_GROUP = Bundle.main.object(forInfoDictionaryKey: "AppGroupId") as! String enum WidgetError: Error, Codable { case noLogin diff --git a/mobile/ios/WidgetExtension/Info.plist b/mobile/ios/WidgetExtension/Info.plist index d4e598ee31..15b00baa20 100644 --- a/mobile/ios/WidgetExtension/Info.plist +++ b/mobile/ios/WidgetExtension/Info.plist @@ -2,6 +2,8 @@ + AppGroupId + $(CUSTOM_GROUP_ID) NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/mobile/ios/WidgetExtension/WidgetExtension.entitlements b/mobile/ios/WidgetExtension/WidgetExtension.entitlements index d16dcca065..1328658832 100644 --- a/mobile/ios/WidgetExtension/WidgetExtension.entitlements +++ b/mobile/ios/WidgetExtension/WidgetExtension.entitlements @@ -4,7 +4,7 @@ com.apple.security.application-groups - group.app.immich.share + $(CUSTOM_GROUP_ID) \ No newline at end of file diff --git a/mobile/ios/fastlane/Fastfile b/mobile/ios/fastlane/Fastfile index ff9fc4580f..f5f7f7887a 100644 --- a/mobile/ios/fastlane/Fastfile +++ b/mobile/ios/fastlane/Fastfile @@ -21,6 +21,7 @@ platform :ios do CODE_SIGN_IDENTITY = "Apple Distribution: FUTO Holdings, Inc. (#{TEAM_ID})" BASE_BUNDLE_ID = "app.alextran.immich" DEV_BUNDLE_ID = "tech.futo.immich.testflight" + DEV_GROUP_ID = "group.app.immich.share.testflight" # Helper method to get App Store Connect API key def get_api_key @@ -33,6 +34,13 @@ platform :ios do ) end + # Helper method to assemble xcargs with optional CUSTOM_GROUP_ID override + def build_xcargs(group_id: nil) + args = "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual" + args += " CUSTOM_GROUP_ID='#{group_id}'" if group_id + args + end + # Helper method to get version from pubspec.yaml def get_version_from_pubspec require 'yaml' @@ -89,7 +97,8 @@ end version_number: nil, profile_name_main:, profile_name_share:, - profile_name_widget: + profile_name_widget:, + group_id: nil ) app_identifier = base_bundle_id @@ -97,7 +106,7 @@ end if version_number increment_version_number(version_number: version_number) end - + # Increment build number increment_build_number( build_number: latest_testflight_build_number( @@ -106,14 +115,14 @@ end ) + 1, xcodeproj: "./Runner.xcodeproj" ) - + # Build the app build_app( scheme: "Runner", workspace: "Runner.xcworkspace", configuration: configuration, export_method: "app-store", - xcargs: "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual", + xcargs: build_xcargs(group_id: group_id), export_options: { provisioningProfiles: { "#{app_identifier}" => profile_name_main, @@ -165,7 +174,8 @@ end distribute_external: false, profile_name_main: main_profile_name, profile_name_share: share_profile_name, - profile_name_widget: widget_profile_name + profile_name_widget: widget_profile_name, + group_id: DEV_GROUP_ID ) end @@ -274,7 +284,7 @@ end configuration: "Release", export_method: "app-store", skip_package_ipa: true, - xcargs: "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual", + xcargs: build_xcargs(group_id: DEV_GROUP_ID), export_options: { provisioningProfiles: { DEV_BUNDLE_ID => main_profile_name, diff --git a/mobile/lib/constants/constants.dart b/mobile/lib/constants/constants.dart index 1748a2a57d..409cd38431 100644 --- a/mobile/lib/constants/constants.dart +++ b/mobile/lib/constants/constants.dart @@ -30,7 +30,6 @@ const int kTimelineAssetLoadBatchSize = 1024; const int kTimelineAssetLoadOppositeSize = 64; // Widget keys -const String appShareGroupId = "group.app.immich.share"; const String kWidgetAuthToken = "widget_auth_token"; const String kWidgetServerEndpoint = "widget_server_url"; const String kWidgetCustomHeaders = "widget_custom_headers"; diff --git a/mobile/lib/constants/enums.dart b/mobile/lib/constants/enums.dart index 877145c322..473bd52b03 100644 --- a/mobile/lib/constants/enums.dart +++ b/mobile/lib/constants/enums.dart @@ -18,3 +18,7 @@ enum CleanupStep { selectDate, scan, delete } enum AssetKeepType { none, photosOnly, videosOnly } enum AssetDateAggregation { start, end } + +enum SlideshowLook { contain, cover, blurredBackground } + +enum SlideshowDirection { forward, backward, shuffle } diff --git a/mobile/lib/domain/models/asset/remote_asset.model.dart b/mobile/lib/domain/models/asset/remote_asset.model.dart index a810877dcc..b370825fdd 100644 --- a/mobile/lib/domain/models/asset/remote_asset.model.dart +++ b/mobile/lib/domain/models/asset/remote_asset.model.dart @@ -11,6 +11,7 @@ class RemoteAsset extends BaseAsset { final String ownerId; final String? stackId; final DateTime? uploadedAt; + final DateTime? deletedAt; const RemoteAsset({ required this.id, @@ -31,6 +32,7 @@ class RemoteAsset extends BaseAsset { super.livePhotoVideoId, this.stackId, required super.isEdited, + this.deletedAt, }) : localAssetId = localId; @override @@ -48,6 +50,8 @@ class RemoteAsset extends BaseAsset { @override bool get isEditable => isImage && !isMotionPhoto && !isAnimatedImage; + bool get isTrashed => deletedAt != null; + @override String toString() { return '''Asset { @@ -86,7 +90,8 @@ class RemoteAsset extends BaseAsset { thumbHash == other.thumbHash && visibility == other.visibility && stackId == other.stackId && - uploadedAt == other.uploadedAt; + uploadedAt == other.uploadedAt && + deletedAt == other.deletedAt; } @override @@ -98,7 +103,8 @@ class RemoteAsset extends BaseAsset { thumbHash.hashCode ^ visibility.hashCode ^ stackId.hashCode ^ - uploadedAt.hashCode; + uploadedAt.hashCode ^ + deletedAt.hashCode; RemoteAsset copyWith({ String? id, @@ -119,6 +125,7 @@ class RemoteAsset extends BaseAsset { String? livePhotoVideoId, String? stackId, bool? isEdited, + DateTime? deletedAt, }) { return RemoteAsset( id: id ?? this.id, @@ -139,6 +146,7 @@ class RemoteAsset extends BaseAsset { livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, stackId: stackId ?? this.stackId, isEdited: isEdited ?? this.isEdited, + deletedAt: deletedAt ?? this.deletedAt, ); } } @@ -156,6 +164,7 @@ class RemoteAssetExif extends RemoteAsset { required super.createdAt, required super.updatedAt, super.uploadedAt, + super.deletedAt, super.width, super.height, super.durationMs, @@ -193,6 +202,7 @@ class RemoteAssetExif extends RemoteAsset { DateTime? createdAt, DateTime? updatedAt, DateTime? uploadedAt, + DateTime? deletedAt, int? width, int? height, int? durationMs, @@ -214,6 +224,7 @@ class RemoteAssetExif extends RemoteAsset { createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt, uploadedAt: uploadedAt ?? this.uploadedAt, + deletedAt: deletedAt ?? this.deletedAt, width: width ?? this.width, height: height ?? this.height, durationMs: durationMs ?? this.durationMs, diff --git a/mobile/lib/domain/models/config/album_config.dart b/mobile/lib/domain/models/config/album_config.dart new file mode 100644 index 0000000000..a83fc32fbf --- /dev/null +++ b/mobile/lib/domain/models/config/album_config.dart @@ -0,0 +1,26 @@ +import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; + +class AlbumConfig { + final AlbumSortMode sortMode; + final bool isReverse; + final bool isGrid; + + const AlbumConfig({this.sortMode = AlbumSortMode.mostRecent, this.isReverse = true, this.isGrid = false}); + + AlbumConfig copyWith({AlbumSortMode? sortMode, bool? isReverse, bool? isGrid}) => AlbumConfig( + sortMode: sortMode ?? this.sortMode, + isReverse: isReverse ?? this.isReverse, + isGrid: isGrid ?? this.isGrid, + ); + + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AlbumConfig && other.sortMode == sortMode && other.isReverse == isReverse && other.isGrid == isGrid); + + @override + int get hashCode => Object.hash(sortMode, isReverse, isGrid); + + @override + String toString() => 'AlbumConfig(sortMode: $sortMode, isReverse: $isReverse, isGrid: $isGrid)'; +} diff --git a/mobile/lib/domain/models/config/app_config.dart b/mobile/lib/domain/models/config/app_config.dart index beca1c21e7..1baa368df4 100644 --- a/mobile/lib/domain/models/config/app_config.dart +++ b/mobile/lib/domain/models/config/app_config.dart @@ -1,6 +1,9 @@ +import 'package:immich_mobile/domain/models/config/album_config.dart'; +import 'package:immich_mobile/domain/models/config/backup_config.dart'; import 'package:immich_mobile/domain/models/config/cleanup_config.dart'; import 'package:immich_mobile/domain/models/config/image_config.dart'; import 'package:immich_mobile/domain/models/config/map_config.dart'; +import 'package:immich_mobile/domain/models/config/slideshow_config.dart'; import 'package:immich_mobile/domain/models/config/theme_config.dart'; import 'package:immich_mobile/domain/models/config/timeline_config.dart'; import 'package:immich_mobile/domain/models/config/viewer_config.dart'; @@ -12,6 +15,9 @@ class AppConfig { final TimelineConfig timeline; final ImageConfig image; final ViewerConfig viewer; + final SlideshowConfig slideshow; + final AlbumConfig album; + final BackupConfig backup; const AppConfig({ this.theme = const .new(), @@ -20,6 +26,9 @@ class AppConfig { this.timeline = const .new(), this.image = const .new(), this.viewer = const .new(), + this.slideshow = const .new(), + this.album = const .new(), + this.backup = const .new(), }); AppConfig copyWith({ @@ -29,6 +38,9 @@ class AppConfig { TimelineConfig? timeline, ImageConfig? image, ViewerConfig? viewer, + SlideshowConfig? slideshow, + AlbumConfig? album, + BackupConfig? backup, }) => .new( theme: theme ?? this.theme, cleanup: cleanup ?? this.cleanup, @@ -36,6 +48,9 @@ class AppConfig { timeline: timeline ?? this.timeline, image: image ?? this.image, viewer: viewer ?? this.viewer, + slideshow: slideshow ?? this.slideshow, + album: album ?? this.album, + backup: backup ?? this.backup, ); @override @@ -47,12 +62,15 @@ class AppConfig { other.map == map && other.timeline == timeline && other.image == image && - other.viewer == viewer); + other.viewer == viewer && + other.slideshow == slideshow && + other.album == album && + other.backup == backup); @override - int get hashCode => Object.hash(theme, cleanup, map, timeline, image, viewer); + int get hashCode => Object.hash(theme, cleanup, map, timeline, image, viewer, slideshow, album, backup); @override String toString() => - 'AppConfig(theme: $theme, cleanup: $cleanup, map: $map, timeline: $timeline, image: $image, viewer: $viewer)'; + 'AppConfig(theme: $theme, cleanup: $cleanup, map: $map, timeline: $timeline, image: $image, viewer: $viewer, slideshow: $slideshow, album: $album, backup: $backup)'; } diff --git a/mobile/lib/domain/models/config/backup_config.dart b/mobile/lib/domain/models/config/backup_config.dart new file mode 100644 index 0000000000..19f91a4ed7 --- /dev/null +++ b/mobile/lib/domain/models/config/backup_config.dart @@ -0,0 +1,52 @@ +class BackupConfig { + final bool enabled; + final bool useCellularForVideos; + final bool useCellularForPhotos; + final bool requireCharging; + final int triggerDelay; + final bool syncAlbums; + + const BackupConfig({ + this.enabled = false, + this.useCellularForVideos = false, + this.useCellularForPhotos = false, + this.requireCharging = false, + this.triggerDelay = 30, + this.syncAlbums = false, + }); + + BackupConfig copyWith({ + bool? enabled, + bool? useCellularForVideos, + bool? useCellularForPhotos, + bool? requireCharging, + int? triggerDelay, + bool? syncAlbums, + }) => BackupConfig( + enabled: enabled ?? this.enabled, + useCellularForVideos: useCellularForVideos ?? this.useCellularForVideos, + useCellularForPhotos: useCellularForPhotos ?? this.useCellularForPhotos, + requireCharging: requireCharging ?? this.requireCharging, + triggerDelay: triggerDelay ?? this.triggerDelay, + syncAlbums: syncAlbums ?? this.syncAlbums, + ); + + @override + bool operator ==(Object other) => + identical(this, other) || + (other is BackupConfig && + other.enabled == enabled && + other.useCellularForVideos == useCellularForVideos && + other.useCellularForPhotos == useCellularForPhotos && + other.requireCharging == requireCharging && + other.triggerDelay == triggerDelay && + other.syncAlbums == syncAlbums); + + @override + int get hashCode => + Object.hash(enabled, useCellularForVideos, useCellularForPhotos, requireCharging, triggerDelay, syncAlbums); + + @override + String toString() => + 'BackupConfig(enabled: $enabled, useCellularForVideos: $useCellularForVideos, useCellularForPhotos: $useCellularForPhotos, requireCharging: $requireCharging, triggerDelay: $triggerDelay, syncAlbums: $syncAlbums)'; +} diff --git a/mobile/lib/domain/models/config/network_config.dart b/mobile/lib/domain/models/config/network_config.dart new file mode 100644 index 0000000000..78f0482a1a --- /dev/null +++ b/mobile/lib/domain/models/config/network_config.dart @@ -0,0 +1,54 @@ +import 'package:flutter/foundation.dart'; + +class NetworkConfig { + final bool autoEndpointSwitching; + final String? preferredWifiName; + final String? localEndpoint; + final List externalEndpointList; + final Map customHeaders; + + const NetworkConfig({ + this.autoEndpointSwitching = false, + this.preferredWifiName, + this.localEndpoint, + this.externalEndpointList = const [], + this.customHeaders = const {}, + }); + + NetworkConfig copyWith({ + bool? autoEndpointSwitching, + String? preferredWifiName, + String? localEndpoint, + List? externalEndpointList, + Map? customHeaders, + }) => NetworkConfig( + autoEndpointSwitching: autoEndpointSwitching ?? this.autoEndpointSwitching, + preferredWifiName: preferredWifiName ?? this.preferredWifiName, + localEndpoint: localEndpoint ?? this.localEndpoint, + externalEndpointList: externalEndpointList ?? this.externalEndpointList, + customHeaders: customHeaders ?? this.customHeaders, + ); + + @override + bool operator ==(Object other) => + identical(this, other) || + (other is NetworkConfig && + other.autoEndpointSwitching == autoEndpointSwitching && + other.preferredWifiName == preferredWifiName && + other.localEndpoint == localEndpoint && + listEquals(other.externalEndpointList, externalEndpointList) && + mapEquals(other.customHeaders, customHeaders)); + + @override + int get hashCode => Object.hash( + autoEndpointSwitching, + preferredWifiName, + localEndpoint, + Object.hashAll(externalEndpointList), + Object.hashAllUnordered(customHeaders.entries.map((e) => Object.hash(e.key, e.value))), + ); + + @override + String toString() => + 'NetworkConfig(autoEndpointSwitching: $autoEndpointSwitching, preferredWifiName: $preferredWifiName, localEndpoint: $localEndpoint, externalEndpointList: $externalEndpointList, customHeaders: $customHeaders)'; +} diff --git a/mobile/lib/domain/models/config/slideshow_config.dart b/mobile/lib/domain/models/config/slideshow_config.dart new file mode 100644 index 0000000000..74c0ac9d38 --- /dev/null +++ b/mobile/lib/domain/models/config/slideshow_config.dart @@ -0,0 +1,48 @@ +import 'package:immich_mobile/constants/enums.dart'; + +class SlideshowConfig { + final bool transition; + final bool repeat; + final int duration; + final SlideshowLook look; + final SlideshowDirection direction; + + const SlideshowConfig({ + this.transition = true, + this.repeat = true, + this.duration = 5, + this.look = SlideshowLook.contain, + this.direction = SlideshowDirection.forward, + }); + + SlideshowConfig copyWith({ + bool? transition, + bool? repeat, + int? duration, + SlideshowLook? look, + SlideshowDirection? direction, + }) => SlideshowConfig( + transition: transition ?? this.transition, + repeat: repeat ?? this.repeat, + duration: duration ?? this.duration, + look: look ?? this.look, + direction: direction ?? this.direction, + ); + + @override + bool operator ==(Object other) => + identical(this, other) || + (other is SlideshowConfig && + other.transition == transition && + other.repeat == repeat && + other.duration == duration && + other.look == look && + other.direction == direction); + + @override + int get hashCode => Object.hash(transition, repeat, duration, look, direction); + + @override + String toString() => + 'SlideshowConfig(transition: $transition, repeat: $repeat, duration: $duration, look: $look, direction: $direction)'; +} diff --git a/mobile/lib/domain/models/config/system_config.dart b/mobile/lib/domain/models/config/system_config.dart index cbad77695d..7d8fef6dd8 100644 --- a/mobile/lib/domain/models/config/system_config.dart +++ b/mobile/lib/domain/models/config/system_config.dart @@ -1,18 +1,22 @@ +import 'package:immich_mobile/domain/models/config/network_config.dart'; import 'package:immich_mobile/domain/models/log.model.dart'; class SystemConfig { final LogLevel logLevel; + final NetworkConfig network; - const SystemConfig({this.logLevel = .info}); + const SystemConfig({this.logLevel = .info, this.network = const .new()}); - SystemConfig copyWith({LogLevel? logLevel}) => SystemConfig(logLevel: logLevel ?? this.logLevel); + SystemConfig copyWith({LogLevel? logLevel, NetworkConfig? network}) => + SystemConfig(logLevel: logLevel ?? this.logLevel, network: network ?? this.network); @override - bool operator ==(Object other) => identical(this, other) || (other is SystemConfig && other.logLevel == logLevel); + bool operator ==(Object other) => + identical(this, other) || (other is SystemConfig && other.logLevel == logLevel && other.network == network); @override - int get hashCode => logLevel.hashCode; + int get hashCode => Object.hash(logLevel, network); @override - String toString() => 'SystemConfig(logLevel: $logLevel)'; + String toString() => 'SystemConfig(logLevel: $logLevel, network: $network)'; } diff --git a/mobile/lib/domain/models/metadata_key.dart b/mobile/lib/domain/models/metadata_key.dart index 61a3cebc8a..541c538169 100644 --- a/mobile/lib/domain/models/metadata_key.dart +++ b/mobile/lib/domain/models/metadata_key.dart @@ -8,6 +8,7 @@ import 'package:immich_mobile/domain/models/config/app_config.dart'; import 'package:immich_mobile/domain/models/config/system_config.dart'; import 'package:immich_mobile/domain/models/log.model.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart'; +import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; enum MetadataDomain { appConfig('config.app'), @@ -34,6 +35,41 @@ enum MetadataKey { viewerAutoPlayVideo(.appConfig, 'viewer.autoPlayVideo', true), viewerTapToNavigate(.appConfig, 'viewer.tapToNavigate', false), + // Network + networkAutoEndpointSwitching(.systemConfig, 'network.autoEndpointSwitching', false), + networkPreferredWifiName(.systemConfig, 'network.preferredWifiName', ''), + networkLocalEndpoint(.systemConfig, 'network.localEndpoint', ''), + networkExternalEndpointList>( + .systemConfig, + 'network.externalEndpointList', + [], + _ListCodec(_PrimitiveCodec.string), + ), + networkCustomHeaders>( + .systemConfig, + 'network.customHeaders', + {}, + _MapCodec(_PrimitiveCodec.string, _PrimitiveCodec.string), + ), + + // Album + albumSortMode( + .appConfig, + 'album.sortMode', + AlbumSortMode.mostRecent, + _EnumCodec(AlbumSortMode.values), + ), + albumIsReverse(.appConfig, 'album.isReverse', true), + albumIsGrid(.appConfig, 'album.isGrid', false), + + // Backup + backupEnabled(.appConfig, 'backup.enabled', false), + backupUseCellularForVideos(.appConfig, 'backup.useCellularForVideos', false), + backupUseCellularForPhotos(.appConfig, 'backup.useCellularForPhotos', false), + backupRequireCharging(.appConfig, 'backup.requireCharging', false), + backupTriggerDelay(.appConfig, 'backup.triggerDelay', 30), + backupSyncAlbums(.appConfig, 'backup.syncAlbums', false), + // Timeline timelineTilesPerRow(.appConfig, 'timeline.tilesPerRow', 4), timelineGroupAssetsBy( @@ -64,7 +100,19 @@ enum MetadataKey { ), cleanupKeepAlbumIds>(.appConfig, 'cleanup.keepAlbumIds', [], _ListCodec(_PrimitiveCodec.string)), cleanupCutoffDaysAgo(.appConfig, 'cleanup.cutoffDaysAgo', -1), - cleanupDefaultsInitialized(.appConfig, 'cleanup.defaultsInitialized', false); + cleanupDefaultsInitialized(.appConfig, 'cleanup.defaultsInitialized', false), + + // Slideshow + slideshowTransition(.appConfig, 'slideshow.transition', true), + slideshowRepeat(.appConfig, 'slideshow.repeat', true), + slideshowDuration(.appConfig, 'slideshow.duration', 5), + slideshowLook(.appConfig, 'slideshow.look', SlideshowLook.contain, _EnumCodec(SlideshowLook.values)), + slideshowDirection( + .appConfig, + 'slideshow.direction', + SlideshowDirection.forward, + _EnumCodec(SlideshowDirection.values), + ); final MetadataDomain domain; final String name; @@ -131,6 +179,47 @@ final class _DateTimeCodec extends _MetadataCodec { DateTime? decode(String raw) => DateTime.tryParse(raw); } +final class _MapCodec extends _MetadataCodec> { + final _MetadataCodec _keyCodec; + final _MetadataCodec _valueCodec; + + const _MapCodec(this._keyCodec, this._valueCodec); + + @override + String encode(Map value) { + final entries = {}; + value.forEach((k, v) => entries[_keyCodec.encode(k)] = _valueCodec.encode(v)); + return jsonEncode(entries); + } + + @override + Map? decode(String raw) { + try { + final decoded = jsonDecode(raw); + if (decoded is! Map) { + return null; + } + final result = {}; + for (final entry in decoded.entries) { + final rawKey = entry.key; + final rawValue = entry.value; + if (rawKey is! String || rawValue is! String) { + return null; + } + final k = _keyCodec.decode(rawKey); + final v = _valueCodec.decode(rawValue); + if (k == null || v == null) { + return null; + } + result[k] = v; + } + return result; + } on FormatException { + return null; + } + } +} + final class _ListCodec extends _MetadataCodec> { final _MetadataCodec _elementCodec; diff --git a/mobile/lib/domain/models/setting.model.dart b/mobile/lib/domain/models/setting.model.dart index 0dc48de3b1..d6d9e2902b 100644 --- a/mobile/lib/domain/models/setting.model.dart +++ b/mobile/lib/domain/models/setting.model.dart @@ -1,8 +1,7 @@ import 'package:immich_mobile/domain/models/store.model.dart'; enum Setting { - advancedTroubleshooting(StoreKey.advancedTroubleshooting, false), - enableBackup(StoreKey.enableBackup, false); + advancedTroubleshooting(StoreKey.advancedTroubleshooting, false); const Setting(this.storeKey, this.defaultValue); diff --git a/mobile/lib/domain/models/store.model.dart b/mobile/lib/domain/models/store.model.dart index 63281e49da..be1b0c5fb8 100644 --- a/mobile/lib/domain/models/store.model.dart +++ b/mobile/lib/domain/models/store.model.dart @@ -6,36 +6,33 @@ enum StoreKey { version._(0), currentUser._(2), deviceId._(4), - backupRequireCharging._(7), - backupTriggerDelay._(8), serverUrl._(10), accessToken._(11), serverEndpoint._(12), - selectedAlbumSortOrder._(113), advancedTroubleshooting._(114), - selectedAlbumSortReverse._(123), enableHapticFeedback._(126), - customHeaders._(127), - syncAlbums._(131), - - // Auto endpoint switching - autoEndpointSwitching._(132), - preferredWifiName._(133), - localEndpoint._(134), - externalEndpointList._(135), manageLocalMediaAndroid._(137), // Read-only Mode settings readonlyModeEnabled._(138), - albumGridView._(140), - // Experimental stuff - enableBackup._(1003), - useWifiForUploadVideos._(1004), - useWifiForUploadPhotos._(1005), syncMigrationStatus._(1013), // Legacy keys that have been migrated to the new metadata store + legacyBackupRequireCharging._(7), + legacyBackupTriggerDelay._(8), + legacySyncAlbums._(131), + legacyEnableBackup._(1003), + legacyUseWifiForUploadVideos._(1004), + legacyUseWifiForUploadPhotos._(1005), + legacySelectedAlbumSortOrder._(113), + legacySelectedAlbumSortReverse._(123), + legacyAlbumGridView._(140), + legacyAutoEndpointSwitching._(132), + legacyPreferredWifiName._(133), + legacyLocalEndpoint._(134), + legacyExternalEndpointList._(135), + legacyCustomHeaders._(127), legacyLoopVideo._(117), legacyLoadOriginalVideo._(136), legacyAutoPlayVideo._(139), diff --git a/mobile/lib/domain/services/background_worker.service.dart b/mobile/lib/domain/services/background_worker.service.dart index 0c8746700c..bcefadaf0e 100644 --- a/mobile/lib/domain/services/background_worker.service.dart +++ b/mobile/lib/domain/services/background_worker.service.dart @@ -11,15 +11,14 @@ import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart'; import 'package:immich_mobile/platform/background_worker_api.g.dart'; import 'package:immich_mobile/platform/background_worker_lock_api.g.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart' show nativeSyncApiProvider; import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/auth.service.dart'; import 'package:immich_mobile/services/foreground_upload.service.dart'; import 'package:immich_mobile/services/localization.service.dart'; @@ -39,16 +38,15 @@ class BackgroundWorkerFgService { Future saveNotificationMessage(String title, String body) => _foregroundHostApi.saveNotificationMessage(title, body); - Future configure({int? minimumDelaySeconds, bool? requireCharging}) => _foregroundHostApi.configure( - BackgroundWorkerSettings( - minimumDelaySeconds: - minimumDelaySeconds ?? - Store.get(AppSettingsEnum.backupTriggerDelay.storeKey, AppSettingsEnum.backupTriggerDelay.defaultValue), - requiresCharging: - requireCharging ?? - Store.get(AppSettingsEnum.backupRequireCharging.storeKey, AppSettingsEnum.backupRequireCharging.defaultValue), - ), - ); + Future configure({int? minimumDelaySeconds, bool? requireCharging}) { + final backup = MetadataRepository.instance.appConfig.backup; + return _foregroundHostApi.configure( + BackgroundWorkerSettings( + minimumDelaySeconds: minimumDelaySeconds ?? backup.triggerDelay, + requiresCharging: requireCharging ?? backup.requireCharging, + ), + ); + } Future disable() => _foregroundHostApi.disable(); } @@ -71,7 +69,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { BackgroundWorkerFlutterApi.setUp(this); } - bool get _isBackupEnabled => _ref?.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup) ?? false; + bool get _isBackupEnabled => MetadataRepository.instance.appConfig.backup.enabled; Future init() async { try { diff --git a/mobile/lib/domain/services/local_sync.service.dart b/mobile/lib/domain/services/local_sync.service.dart index 34300dee3d..23f9e3f78d 100644 --- a/mobile/lib/domain/services/local_sync.service.dart +++ b/mobile/lib/domain/services/local_sync.service.dart @@ -9,10 +9,10 @@ import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart'; import 'package:immich_mobile/platform/native_sync_api.g.dart'; -import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; +import 'package:immich_mobile/repositories/asset_media.repository.dart'; +import 'package:immich_mobile/repositories/permission.repository.dart'; import 'package:immich_mobile/utils/datetime_helpers.dart'; import 'package:immich_mobile/utils/diff.dart'; import 'package:logging/logging.dart'; @@ -23,29 +23,29 @@ class LocalSyncService { final DriftLocalAssetRepository _localAssetRepository; final NativeSyncApi _nativeSyncApi; final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository; - final LocalFilesManagerRepository _localFilesManager; - final StorageRepository _storageRepository; + final AssetMediaRepository _assetMediaRepository; + final IPermissionRepository _permissionRepository; final Logger _log = Logger("DeviceSyncService"); LocalSyncService({ required DriftLocalAlbumRepository localAlbumRepository, required DriftLocalAssetRepository localAssetRepository, required DriftTrashedLocalAssetRepository trashedLocalAssetRepository, - required LocalFilesManagerRepository localFilesManager, - required StorageRepository storageRepository, + required AssetMediaRepository assetMediaRepository, + required IPermissionRepository permissionRepository, required NativeSyncApi nativeSyncApi, }) : _localAlbumRepository = localAlbumRepository, _localAssetRepository = localAssetRepository, _trashedLocalAssetRepository = trashedLocalAssetRepository, - _localFilesManager = localFilesManager, - _storageRepository = storageRepository, + _assetMediaRepository = assetMediaRepository, + _permissionRepository = permissionRepository, _nativeSyncApi = nativeSyncApi; Future sync({bool full = false}) async { final Stopwatch stopwatch = Stopwatch()..start(); try { if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) { - final hasPermission = await _localFilesManager.hasManageMediaPermission(); + final hasPermission = await _permissionRepository.hasManageMediaPermission(); if (hasPermission) { await _syncTrashedAssets(); } else { @@ -373,7 +373,7 @@ class LocalSyncService { final assetsToRestore = await _trashedLocalAssetRepository.getToRestore(); if (assetsToRestore.isNotEmpty) { - final restoredIds = await _localFilesManager.restoreAssetsFromTrash(assetsToRestore); + final restoredIds = await _assetMediaRepository.restoreAssetsFromTrash(assetsToRestore); await _trashedLocalAssetRepository.applyRestoredAssets(restoredIds); } else { _log.info("syncTrashedAssets, No remote assets found for restoration"); @@ -381,15 +381,15 @@ class LocalSyncService { final localAssetsToTrash = await _trashedLocalAssetRepository.getToTrash(); if (localAssetsToTrash.isNotEmpty) { - final mediaUrls = await Future.wait( - localAssetsToTrash.values - .expand((e) => e) - .map((localAsset) => _storageRepository.getAssetEntityForAsset(localAsset).then((e) => e?.getMediaUrl())), - ); - _log.info("Moving to trash ${mediaUrls.join(", ")} assets"); - final result = await _localFilesManager.moveToTrash(mediaUrls.nonNulls.toList()); - if (result) { - await _trashedLocalAssetRepository.trashLocalAsset(localAssetsToTrash); + final localIds = localAssetsToTrash.values.expand((assets) => assets).map((asset) => asset.id).toList(); + _log.info("Moving to trash ${localIds.join(", ")} assets"); + final movedIds = await _assetMediaRepository.deleteAll(localIds); + if (movedIds.isNotEmpty) { + final movedAssetsByAlbum = localAssetsToTrash.map( + (albumId, assets) => MapEntry(albumId, assets.where((asset) => movedIds.contains(asset.id)).toList()), + )..removeWhere((_, assets) => assets.isEmpty); + + await _trashedLocalAssetRepository.trashLocalAsset(movedAssetsByAlbum); } } else { _log.info("syncTrashedAssets, No assets found in backup-enabled albums for move to trash"); diff --git a/mobile/lib/domain/services/remote_album.service.dart b/mobile/lib/domain/services/remote_album.service.dart index d0af52dcfd..e873a7631f 100644 --- a/mobile/lib/domain/services/remote_album.service.dart +++ b/mobile/lib/domain/services/remote_album.service.dart @@ -9,12 +9,47 @@ import 'package:immich_mobile/infrastructure/repositories/remote_album.repositor import 'package:immich_mobile/models/albums/album_search.model.dart'; import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; import 'package:immich_mobile/repositories/drift_album_api_repository.dart'; +import 'package:immich_mobile/services/foreground_upload.service.dart'; +import 'package:logging/logging.dart'; + +/// Categorizes a heterogeneous asset selection into the candidates that can +/// be added to an album immediately (already on the server) and the local-only +/// candidates that must be uploaded first. +class AlbumAssetCandidates { + final List remoteAssetIds; + final List localAssetsToUpload; + + const AlbumAssetCandidates({required this.remoteAssetIds, required this.localAssetsToUpload}); +} class RemoteAlbumService { + static final _logger = Logger('RemoteAlbumService'); + final DriftRemoteAlbumRepository _repository; final DriftAlbumApiRepository _albumApiRepository; + final ForegroundUploadService _uploadService; - const RemoteAlbumService(this._repository, this._albumApiRepository); + const RemoteAlbumService(this._repository, this._albumApiRepository, this._uploadService); + + /// Categorizes a heterogeneous asset selection into already-on-server IDs + /// and local assets that still need to be uploaded. + static AlbumAssetCandidates categorizeCandidates(Iterable assets) { + final remoteIds = []; + final localToUpload = []; + for (final asset in assets) { + if (asset is RemoteAsset) { + remoteIds.add(asset.id); + } else if (asset is LocalAsset) { + final remoteId = asset.remoteId; + if (remoteId != null) { + remoteIds.add(remoteId); + } else { + localToUpload.add(asset); + } + } + } + return AlbumAssetCandidates(remoteAssetIds: remoteIds, localAssetsToUpload: localToUpload); + } Stream watchAlbum(String albumId) { return _repository.watchAlbum(albumId); @@ -148,6 +183,122 @@ class RemoteAlbumService { return album.added.length; } + /// !TODO The name here is not clear as we have addAssets method above, + /// which is only add remote assets to album, for the next PR, we will allow + /// adding local assets from album from the timeline as well with this flow. + /// So saving that for the next refactor + Future addAssetsToAlbum({ + required String albumId, + required UserDto uploader, + required AlbumAssetCandidates candidates, + UploadCallbacks uploadCallbacks = const UploadCallbacks(), + }) async { + int addedCount = 0; + if (candidates.remoteAssetIds.isNotEmpty) { + addedCount += await addAssets(albumId: albumId, assetIds: candidates.remoteAssetIds); + } + if (candidates.localAssetsToUpload.isNotEmpty) { + addedCount += await _uploadAndAddLocals(albumId, uploader, candidates.localAssetsToUpload, uploadCallbacks); + } + return addedCount; + } + + /// Creates an album, seeding it with already-remote asset IDs, then uploads + /// local-only assets and links each one as it finishes. + Future createAlbumWithAssets({ + required String title, + required UserDto owner, + String? description, + AlbumAssetCandidates candidates = const AlbumAssetCandidates(remoteAssetIds: [], localAssetsToUpload: []), + UploadCallbacks uploadCallbacks = const UploadCallbacks(), + }) async { + final album = await createAlbum( + title: title, + owner: owner, + description: description, + assetIds: candidates.remoteAssetIds, + ); + if (candidates.localAssetsToUpload.isNotEmpty) { + await _uploadAndAddLocals(album.id, owner, candidates.localAssetsToUpload, uploadCallbacks); + } + return album; + } + + Future _uploadAndAddLocals( + String albumId, + UserDto uploader, + List localAssets, + UploadCallbacks userCallbacks, + ) async { + int addedCount = 0; + final pendingAdds = >[]; + final localById = {for (final a in localAssets) a.id: a}; + + final wrappedCallbacks = UploadCallbacks( + onProgress: (localId, filename, bytes, totalBytes) => _runUploadCallback( + 'Upload progress callback failed for $localId', + () => userCallbacks.onProgress?.call(localId, filename, bytes, totalBytes), + ), + onICloudProgress: (localId, progress) => _runUploadCallback( + 'iCloud progress callback failed for $localId', + () => userCallbacks.onICloudProgress?.call(localId, progress), + ), + onError: (localId, errorMessage) => _runUploadCallback( + 'Upload error callback failed for $localId', + () => userCallbacks.onError?.call(localId, errorMessage), + ), + onSuccess: (localId, remoteId) { + _runUploadCallback( + 'Upload success callback failed for $localId', + () => userCallbacks.onSuccess?.call(localId, remoteId), + ); + final source = localById[localId]; + if (source == null) { + _logger.warning('Upload success for $localId but source LocalAsset missing; skipping album link'); + return; + } + pendingAdds.add( + _linkUploadedAssetToAlbum(albumId, remoteId, uploader, source) + .then((added) { + addedCount += added; + }) + .catchError((Object error, StackTrace stack) { + _logger.warning('Failed to add uploaded asset $remoteId to album $albumId', error, stack); + }), + ); + }, + ); + + await _uploadService.uploadManual(localAssets, callbacks: wrappedCallbacks); + await Future.wait(pendingAdds); + return addedCount; + } + + void _runUploadCallback(String message, void Function() callback) { + try { + callback(); + } catch (error, stack) { + _logger.warning(message, error, stack); + } + } + + /// Links a freshly-uploaded asset to an album, ensuring the local DB + /// reflects the change without waiting for the next sync. We call the API + /// (server is the source of truth), then upsert a placeholder + /// `remote_asset_entity` row from the local source so the FK-protected + /// junction insert succeeds. Sync overwrites the placeholder later with + /// the authoritative server data. + Future _linkUploadedAssetToAlbum(String albumId, String remoteId, UserDto uploader, LocalAsset source) async { + final result = await _albumApiRepository.addAssets(albumId, [remoteId]); + if (result.added.isEmpty) { + return 0; + } + + await _repository.upsertRemoteAssetStub(remoteId: remoteId, ownerId: uploader.id, source: source); + await _repository.addAssets(albumId, result.added); + return result.added.length; + } + Future deleteAlbum(String albumId) async { await _albumApiRepository.deleteAlbum(albumId); diff --git a/mobile/lib/domain/services/sync_stream.service.dart b/mobile/lib/domain/services/sync_stream.service.dart index 9c8bac4c92..862d4c165c 100644 --- a/mobile/lib/domain/services/sync_stream.service.dart +++ b/mobile/lib/domain/services/sync_stream.service.dart @@ -9,12 +9,12 @@ import 'package:immich_mobile/domain/models/sync_event.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_migration.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart'; -import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; +import 'package:immich_mobile/repositories/asset_media.repository.dart'; +import 'package:immich_mobile/repositories/permission.repository.dart'; import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/utils/semver.dart'; import 'package:logging/logging.dart'; @@ -34,8 +34,8 @@ class SyncStreamService { final SyncStreamRepository _syncStreamRepository; final DriftLocalAssetRepository _localAssetRepository; final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository; - final LocalFilesManagerRepository _localFilesManager; - final StorageRepository _storageRepository; + final AssetMediaRepository _assetMediaRepository; + final IPermissionRepository _permissionRepository; final SyncMigrationRepository _syncMigrationRepository; final ApiService _api; final bool Function()? _cancelChecker; @@ -45,8 +45,8 @@ class SyncStreamService { required SyncStreamRepository syncStreamRepository, required DriftLocalAssetRepository localAssetRepository, required DriftTrashedLocalAssetRepository trashedLocalAssetRepository, - required LocalFilesManagerRepository localFilesManager, - required StorageRepository storageRepository, + required AssetMediaRepository assetMediaRepository, + required IPermissionRepository permissionRepository, required SyncMigrationRepository syncMigrationRepository, required ApiService api, bool Function()? cancelChecker, @@ -54,8 +54,8 @@ class SyncStreamService { _syncStreamRepository = syncStreamRepository, _localAssetRepository = localAssetRepository, _trashedLocalAssetRepository = trashedLocalAssetRepository, - _localFilesManager = localFilesManager, - _storageRepository = storageRepository, + _assetMediaRepository = assetMediaRepository, + _permissionRepository = permissionRepository, _syncMigrationRepository = syncMigrationRepository, _api = api, _cancelChecker = cancelChecker; @@ -500,22 +500,22 @@ class SyncStreamService { } Future _trashLocalAssets(Map> localAssetsToTrash) async { - final mediaUrls = await Future.wait( - localAssetsToTrash.values - .expand((e) => e) - .map((localAsset) => _storageRepository.getAssetEntityForAsset(localAsset).then((e) => e?.getMediaUrl())), - ); - _logger.info("Moving to trash ${mediaUrls.join(", ")} assets"); - final result = await _localFilesManager.moveToTrash(mediaUrls.nonNulls.toList()); - if (result) { - await _trashedLocalAssetRepository.trashLocalAsset(localAssetsToTrash); + final localIds = localAssetsToTrash.values.expand((assets) => assets).map((asset) => asset.id).toList(); + _logger.info("Moving to trash ${localIds.join(", ")} assets"); + final movedIds = await _assetMediaRepository.deleteAll(localIds); + if (movedIds.isNotEmpty) { + final movedAssetsByAlbum = localAssetsToTrash.map( + (albumId, assets) => MapEntry(albumId, assets.where((asset) => movedIds.contains(asset.id)).toList()), + )..removeWhere((_, assets) => assets.isEmpty); + + await _trashedLocalAssetRepository.trashLocalAsset(movedAssetsByAlbum); } } Future _applyRemoteRestoreToLocal() async { final assetsToRestore = await _trashedLocalAssetRepository.getToRestore(); if (assetsToRestore.isNotEmpty) { - final restoredIds = await _localFilesManager.restoreAssetsFromTrash(assetsToRestore); + final restoredIds = await _assetMediaRepository.restoreAssetsFromTrash(assetsToRestore); await _trashedLocalAssetRepository.applyRestoredAssets(restoredIds); } else { _logger.info("No remote assets found for restoration"); @@ -523,7 +523,7 @@ class SyncStreamService { } Future _syncAssetTrashStatus(List remoteIds) async { - if (!(await _localFilesManager.hasManageMediaPermission())) { + if (!(await _permissionRepository.hasManageMediaPermission())) { _logger.warning("Syncing asset trash status cannot proceed because MANAGE_MEDIA permission is missing"); return; } @@ -533,7 +533,7 @@ class SyncStreamService { } Future _syncAssetDeletion(List remoteIds) async { - if (!(await _localFilesManager.hasManageMediaPermission())) { + if (!(await _permissionRepository.hasManageMediaPermission())) { _logger.warning("Syncing asset deletion cannot proceed because MANAGE_MEDIA permission is missing"); return; } diff --git a/mobile/lib/domain/services/tag.service.dart b/mobile/lib/domain/services/tag.service.dart new file mode 100644 index 0000000000..6eeb83d4cb --- /dev/null +++ b/mobile/lib/domain/services/tag.service.dart @@ -0,0 +1,31 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/tag.model.dart'; +import 'package:immich_mobile/infrastructure/repositories/tags_api.repository.dart'; + +final tagServiceProvider = Provider((ref) => TagService(ref.watch(tagsApiRepositoryProvider))); + +class TagService { + final TagsApiRepository _repository; + + const TagService(this._repository); + + Future bulkTagAssets(List assetIds, List tagIds) async { + return _repository.bulkTagAssets(assetIds, tagIds); + } + + Future> getAllTags() async { + final dtos = await _repository.getAllTags(); + if (dtos == null) { + return {}; + } + return dtos.map((dto) => Tag.fromDto(dto)).toSet(); + } + + Future> upsertTags(List tags) async { + final dtos = await _repository.upsertTags(tags); + if (dtos == null) { + return []; + } + return dtos.map((dto) => Tag.fromDto(dto)).toList(); + } +} diff --git a/mobile/lib/extensions/string_extensions.dart b/mobile/lib/extensions/string_extensions.dart index d30c221f96..792b932df2 100644 --- a/mobile/lib/extensions/string_extensions.dart +++ b/mobile/lib/extensions/string_extensions.dart @@ -4,6 +4,8 @@ extension StringExtension on String { String capitalize() { return split(" ").map((str) => str.isEmpty ? str : str[0].toUpperCase() + str.substring(1)).join(" "); } + + String? get nullIfEmpty => isEmpty ? null : this; } extension DurationExtension on String { diff --git a/mobile/lib/infrastructure/entities/remote_asset.entity.dart b/mobile/lib/infrastructure/entities/remote_asset.entity.dart index 8644667168..ad1cec5641 100644 --- a/mobile/lib/infrastructure/entities/remote_asset.entity.dart +++ b/mobile/lib/infrastructure/entities/remote_asset.entity.dart @@ -74,5 +74,6 @@ extension RemoteAssetEntityDataDomainEx on RemoteAssetEntityData { localId: localId, stackId: stackId, isEdited: isEdited, + deletedAt: deletedAt, ); } diff --git a/mobile/lib/infrastructure/repositories/metadata.repository.dart b/mobile/lib/infrastructure/repositories/metadata.repository.dart index d8c8f55898..fa1d275026 100644 --- a/mobile/lib/infrastructure/repositories/metadata.repository.dart +++ b/mobile/lib/infrastructure/repositories/metadata.repository.dart @@ -2,6 +2,7 @@ import 'package:drift/drift.dart'; import 'package:immich_mobile/domain/models/config/app_config.dart'; import 'package:immich_mobile/domain/models/config/system_config.dart'; import 'package:immich_mobile/domain/models/metadata_key.dart'; +import 'package:immich_mobile/extensions/string_extensions.dart'; import 'package:immich_mobile/infrastructure/entities/metadata.entity.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; @@ -139,9 +140,38 @@ extension on MetadataDomain { autoPlayVideo: repo._read(.viewerAutoPlayVideo), tapToNavigate: repo._read(.viewerTapToNavigate), ), + slideshow: .new( + transition: repo._read(.slideshowTransition), + repeat: repo._read(.slideshowRepeat), + duration: repo._read(.slideshowDuration), + look: repo._read(.slideshowLook), + direction: repo._read(.slideshowDirection), + ), + album: .new( + sortMode: repo._read(.albumSortMode), + isReverse: repo._read(.albumIsReverse), + isGrid: repo._read(.albumIsGrid), + ), + backup: .new( + enabled: repo._read(.backupEnabled), + useCellularForVideos: repo._read(.backupUseCellularForVideos), + useCellularForPhotos: repo._read(.backupUseCellularForPhotos), + requireCharging: repo._read(.backupRequireCharging), + triggerDelay: repo._read(.backupTriggerDelay), + syncAlbums: repo._read(.backupSyncAlbums), + ), ); case .systemConfig: - repo._systemConfig = .new(logLevel: repo._read(.logLevel)); + repo._systemConfig = .new( + logLevel: repo._read(.logLevel), + network: .new( + autoEndpointSwitching: repo._read(.networkAutoEndpointSwitching), + preferredWifiName: repo._read(.networkPreferredWifiName).nullIfEmpty, + localEndpoint: repo._read(.networkLocalEndpoint).nullIfEmpty, + externalEndpointList: repo._read(.networkExternalEndpointList), + customHeaders: repo._read(.networkCustomHeaders), + ), + ); } } } diff --git a/mobile/lib/infrastructure/repositories/remote_album.repository.dart b/mobile/lib/infrastructure/repositories/remote_album.repository.dart index c3b972d85f..71d96bb0cb 100644 --- a/mobile/lib/infrastructure/repositories/remote_album.repository.dart +++ b/mobile/lib/infrastructure/repositories/remote_album.repository.dart @@ -10,6 +10,7 @@ import 'package:immich_mobile/infrastructure/entities/remote_album.entity.drift. import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; enum SortRemoteAlbumsBy { id, updatedAt } @@ -159,7 +160,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { createdAt: Value(album.createdAt), updatedAt: Value(album.updatedAt), description: Value(album.description), - thumbnailAssetId: Value(album.thumbnailAssetId), + thumbnailAssetId: Value(album.thumbnailAssetId ?? (assetIds.isNotEmpty ? assetIds.first : null)), isActivityEnabled: Value(album.isActivityEnabled), order: Value(album.order), ); @@ -274,17 +275,59 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { } Future addAssets(String albumId, List assetIds) async { + if (assetIds.isEmpty) { + return 0; + } + final albumAssets = assetIds.map( (assetId) => RemoteAlbumAssetEntityCompanion(albumId: Value(albumId), assetId: Value(assetId)), ); - await _db.batch((batch) { - batch.insertAll(_db.remoteAlbumAssetEntity, albumAssets); + await _db.transaction(() async { + await _db.batch((batch) { + batch.insertAll(_db.remoteAlbumAssetEntity, albumAssets); + }); + + final album = _db.update(_db.remoteAlbumEntity) + ..where((row) => row.id.equals(albumId) & row.thumbnailAssetId.isNull()); + + await album.write(RemoteAlbumEntityCompanion(thumbnailAssetId: Value(assetIds.first))); }); return assetIds.length; } + /// Inserts a placeholder `remote_asset_entity` row from a freshly-uploaded + /// local asset. Skips silently if a row with the same id or + /// (owner_id, checksum) already exists — sync will overwrite with the + /// authoritative server data once the AssetUploadReadyV1 event is processed. + Future upsertRemoteAssetStub({ + required String remoteId, + required String ownerId, + required LocalAsset source, + }) async { + await _db + .into(_db.remoteAssetEntity) + .insert( + RemoteAssetEntityCompanion( + id: Value(remoteId), + ownerId: Value(ownerId), + checksum: Value(source.checksum ?? remoteId), + name: Value(source.name), + type: Value(source.type), + createdAt: Value(source.createdAt), + updatedAt: Value(source.updatedAt), + width: Value(source.width), + height: Value(source.height), + durationMs: Value(source.durationMs), + isFavorite: Value(source.isFavorite), + visibility: const Value(AssetVisibility.timeline), + isEdited: Value(source.isEdited), + ), + mode: InsertMode.insertOrIgnore, + ); + } + Future addUsers(String albumId, List userIds) { final albumUsers = userIds.map( (assetId) => RemoteAlbumUserEntityCompanion( diff --git a/mobile/lib/infrastructure/repositories/tags_api.repository.dart b/mobile/lib/infrastructure/repositories/tags_api.repository.dart index e81b79c459..5963fc2f23 100644 --- a/mobile/lib/infrastructure/repositories/tags_api.repository.dart +++ b/mobile/lib/infrastructure/repositories/tags_api.repository.dart @@ -14,4 +14,13 @@ class TagsApiRepository extends ApiRepository { Future?> getAllTags() async { return await _api.getAllTags(); } + + Future bulkTagAssets(List assetIds, List tagIds) async { + final response = await _api.bulkTagAssets(TagBulkAssetsDto(assetIds: assetIds, tagIds: tagIds)); + return response?.count ?? 0; + } + + Future?> upsertTags(List tags) async { + return _api.upsertTags(TagUpsertDto(tags: tags)); + } } diff --git a/mobile/lib/pages/backup/drift_backup_album_selection.page.dart b/mobile/lib/pages/backup/drift_backup_album_selection.page.dart index 93a1d629b8..de37437326 100644 --- a/mobile/lib/pages/backup/drift_backup_album_selection.page.dart +++ b/mobile/lib/pages/backup/drift_backup_album_selection.page.dart @@ -8,13 +8,13 @@ import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/domain/services/sync_linked_album.service.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/backup/backup_album.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/widgets/backup/drift_album_info_list_tile.dart'; import 'package:immich_mobile/widgets/common/search_field.dart'; import 'package:logging/logging.dart'; @@ -43,7 +43,7 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState p.totalCount)); @@ -55,7 +55,7 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState a.backupSelection == BackupSelection.selected) @@ -103,7 +103,7 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState p.totalCount)); final totalChanged = currentTotalAssetCount != _initialTotalAssetCount; diff --git a/mobile/lib/pages/backup/drift_backup_options.page.dart b/mobile/lib/pages/backup/drift_backup_options.page.dart index 79891d7002..4e8a185955 100644 --- a/mobile/lib/pages/backup/drift_backup_options.page.dart +++ b/mobile/lib/pages/backup/drift_backup_options.page.dart @@ -3,14 +3,12 @@ import 'dart:async'; import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/widgets/settings/backup_settings/drift_backup_settings.dart'; import 'package:logging/logging.dart'; @@ -21,18 +19,20 @@ class DriftBackupOptionsPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { bool hasPopped = false; - final previousWifiReqForVideos = Store.tryGet(StoreKey.useWifiForUploadVideos) ?? false; - final previousWifiReqForPhotos = Store.tryGet(StoreKey.useWifiForUploadPhotos) ?? false; + final previousBackup = ref.read(metadataProvider).appConfig.backup; + final previousCellularForVideos = previousBackup.useCellularForVideos; + final previousCellularForPhotos = previousBackup.useCellularForPhotos; return PopScope( onPopInvokedWithResult: (didPop, result) async { // There is an issue with Flutter where the pop event // can be triggered multiple times, so we guard it with _hasPopped - final currentWifiReqForVideos = Store.tryGet(StoreKey.useWifiForUploadVideos) ?? false; - final currentWifiReqForPhotos = Store.tryGet(StoreKey.useWifiForUploadPhotos) ?? false; + final currentBackup = ref.read(metadataProvider).appConfig.backup; + final currentCellularForVideos = currentBackup.useCellularForVideos; + final currentCellularForPhotos = currentBackup.useCellularForPhotos; - if (currentWifiReqForVideos == previousWifiReqForVideos && - currentWifiReqForPhotos == previousWifiReqForPhotos) { + if (currentCellularForVideos == previousCellularForVideos && + currentCellularForPhotos == previousCellularForPhotos) { return; } @@ -45,7 +45,7 @@ class DriftBackupOptionsPage extends ConsumerWidget { } await ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id); - final isBackupEnabled = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup); + final isBackupEnabled = MetadataRepository.instance.appConfig.backup.enabled; if (!isBackupEnabled) { return; } diff --git a/mobile/lib/pages/common/headers_settings.page.dart b/mobile/lib/pages/common/headers_settings.page.dart index 1fa183456c..e599286dcf 100644 --- a/mobile/lib/pages/common/headers_settings.page.dart +++ b/mobile/lib/pages/common/headers_settings.page.dart @@ -1,14 +1,12 @@ -import 'dart:convert'; - import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart' hide Store; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/domain/models/metadata_key.dart'; import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/providers/api.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart'; class SettingsHeader { String key = ""; @@ -24,17 +22,14 @@ class HeaderSettingsPage extends HookConsumerWidget { final headers = useState>([]); final setInitialHeaders = useState(false); - var headersStr = Store.get(StoreKey.customHeaders, ""); + final storedHeaders = ref.read(metadataProvider).systemConfig.network.customHeaders; if (!setInitialHeaders.value) { - if (headersStr.isNotEmpty) { - var customHeaders = jsonDecode(headersStr) as Map; - customHeaders.forEach((k, v) { - final header = SettingsHeader(); - header.key = k; - header.value = v; - headers.value.add(header); - }); - } + storedHeaders.forEach((k, v) { + final header = SettingsHeader(); + header.key = k; + header.value = v; + headers.value.add(header); + }); // add first one to help the user if (headers.value.isEmpty) { @@ -88,8 +83,8 @@ class HeaderSettingsPage extends HookConsumerWidget { } saveHeaders(WidgetRef ref, List headers) async { - final headersMap = {}; - for (var header in headers) { + final headersMap = {}; + for (final header in headers) { final key = header.key.trim(); final value = header.value.trim(); @@ -99,8 +94,7 @@ class HeaderSettingsPage extends HookConsumerWidget { headersMap[key] = value; } - var encoded = jsonEncode(headersMap); - await Store.put(StoreKey.customHeaders, encoded); + await ref.read(metadataProvider).write(MetadataKey.networkCustomHeaders, headersMap); await ref.read(apiServiceProvider).updateHeaders(); } } diff --git a/mobile/lib/pages/common/splash_screen.page.dart b/mobile/lib/pages/common/splash_screen.page.dart index 51e1812cc8..e9602474c9 100644 --- a/mobile/lib/pages/common/splash_screen.page.dart +++ b/mobile/lib/pages/common/splash_screen.page.dart @@ -12,6 +12,7 @@ import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/generated/codegen_loader.g.dart'; import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; @@ -344,7 +345,7 @@ class SplashScreenPageState extends ConsumerState { await backgroundManager.hashAssets(); } - if (Store.get(StoreKey.syncAlbums, false)) { + if (MetadataRepository.instance.appConfig.backup.syncAlbums) { await backgroundManager.syncLinkedAlbum(); } } catch (e) { @@ -373,7 +374,7 @@ class SplashScreenPageState extends ConsumerState { } Future _resumeBackup(DriftBackupNotifier notifier) async { - final isEnableBackup = Store.get(StoreKey.enableBackup, false); + final isEnableBackup = MetadataRepository.instance.appConfig.backup.enabled; if (isEnableBackup) { final currentUser = Store.tryGet(StoreKey.currentUser); diff --git a/mobile/lib/pages/library/shared_link/shared_link.page.dart b/mobile/lib/pages/library/shared_link/shared_link.page.dart index 261c6975ef..a4f52ebd4c 100644 --- a/mobile/lib/pages/library/shared_link/shared_link.page.dart +++ b/mobile/lib/pages/library/shared_link/shared_link.page.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; -import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/models/shared_link/shared_link.model.dart'; import 'package:immich_mobile/providers/shared_link.provider.dart'; import 'package:immich_mobile/widgets/shared_link/shared_link_item.dart'; @@ -28,71 +27,41 @@ class SharedLinkPage extends HookConsumerWidget { }, []); Widget buildNoShares() { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only(left: 16.0, top: 16.0), - child: const Text( - "shared_link_manage_links", - style: TextStyle(fontSize: 14, color: Colors.grey, fontWeight: FontWeight.bold), - ).tr(), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: const Text("you_dont_have_any_shared_links", style: TextStyle(fontSize: 14)).tr(), - ), - ), - Expanded( - child: Center( - child: Icon(Icons.link_off, size: 100, color: context.themeData.iconTheme.color?.withValues(alpha: 0.5)), - ), - ), - ], + return Center( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.link_off, size: 100, color: Theme.of(context).colorScheme.onSurface.withAlpha(128)), + const SizedBox(height: 20), + const Text("you_dont_have_any_shared_links", style: TextStyle(fontSize: 14)).tr(), + ], + ), ); } Widget buildSharesList(List links) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only(left: 16.0, top: 16.0, bottom: 30.0), - child: Text( - "shared_link_manage_links", - style: context.textTheme.labelLarge?.copyWith(color: context.textTheme.labelLarge?.color?.withAlpha(200)), - ).tr(), - ), - Expanded( - child: LayoutBuilder( - builder: (context, constraints) { - if (constraints.maxWidth > 600) { - // Two column - return GridView.builder( - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - mainAxisExtent: 180, - ), - itemCount: links.length, - itemBuilder: (context, index) { - return SharedLinkItem(links.elementAt(index)); - }, - ); - } - - // Single column - return ListView.builder( - itemCount: links.length, - itemBuilder: (context, index) { - return SharedLinkItem(links.elementAt(index)); - }, - ); - }, - ), - ), - ], + return LayoutBuilder( + builder: (context, constraints) => constraints.maxWidth > 600 + ? GridView.builder( + key: const PageStorageKey('shared-links-grid'), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisExtent: 180, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + ), + padding: const EdgeInsets.all(12), + itemCount: links.length, + itemBuilder: (context, index) => SharedLinkItem(links[index]), + ) + : ListView.separated( + key: const PageStorageKey('shared-links-list'), + padding: const EdgeInsets.symmetric(vertical: 8), + itemCount: links.length, + itemBuilder: (context, index) => SharedLinkItem(links[index]), + separatorBuilder: (context, index) => const Divider(height: 1), + ), ); } diff --git a/mobile/lib/pages/library/shared_link/shared_link_edit.page.dart b/mobile/lib/pages/library/shared_link/shared_link_edit.page.dart index 47a3dd853d..41486d7c98 100644 --- a/mobile/lib/pages/library/shared_link/shared_link_edit.page.dart +++ b/mobile/lib/pages/library/shared_link/shared_link_edit.page.dart @@ -6,15 +6,20 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/models/shared_link/shared_link.model.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/shared_link.provider.dart'; import 'package:immich_mobile/services/shared_link.service.dart'; import 'package:immich_mobile/utils/url_helper.dart'; +import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; +import 'package:share_plus/share_plus.dart'; @RoutePage() class SharedLinkEditPage extends HookConsumerWidget { + static const int maxFutureDate = 365 * 2; + final SharedLink? existingLink; final List? assetsList; final String? albumId; @@ -23,71 +28,82 @@ class SharedLinkEditPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - const padding = 20.0; final themeData = context.themeData; final colorScheme = context.colorScheme; + final externalDomain = ref.watch(serverInfoProvider.select((s) => s.serverConfig.externalDomain)); + final displayServerUrl = externalDomain.isNotEmpty ? externalDomain : getServerUrl(); + final expiryPresets = <(Duration, String)>[ + (Duration.zero, context.t.never), + (const Duration(minutes: 30), context.t.shared_link_edit_expire_after_option_minutes(count: 30)), + (const Duration(hours: 1), context.t.shared_link_edit_expire_after_option_hour), + (const Duration(hours: 6), context.t.shared_link_edit_expire_after_option_hours(count: 6)), + (const Duration(days: 1), context.t.shared_link_edit_expire_after_option_day), + (const Duration(days: 7), context.t.shared_link_edit_expire_after_option_days(count: 7)), + (const Duration(days: 30), context.t.shared_link_edit_expire_after_option_days(count: 30)), + (const Duration(days: 90), context.t.shared_link_edit_expire_after_option_months(count: 3)), + (const Duration(days: 365), context.t.shared_link_edit_expire_after_option_year(count: 1)), + ]; final descriptionController = useTextEditingController(text: existingLink?.description ?? ""); final descriptionFocusNode = useFocusNode(); final passwordController = useTextEditingController(text: existingLink?.password ?? ""); final slugController = useTextEditingController(text: existingLink?.slug ?? ""); final slugFocusNode = useFocusNode(); + useListenable(slugController); final showMetadata = useState(existingLink?.showMetadata ?? true); final allowDownload = useState(existingLink?.allowDownload ?? true); final allowUpload = useState(existingLink?.allowUpload ?? false); - final editExpiry = useState(false); - final expiryAfter = useState(0); + final expiryAfter = useState(existingLink?.expiresAt?.toLocal()); + final selectedPresetIndex = useState(existingLink?.expiresAt == null ? 0 : null); final newShareLink = useState(""); + Widget buildSharedLinkRow({required String leading, required String content}) { + return Row( + children: [ + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Text( + content, + style: TextStyle(color: colorScheme.primary, fontWeight: FontWeight.bold), + ), + ), + ), + const SizedBox(width: 8), + Text(leading, style: const TextStyle(fontWeight: FontWeight.bold)), + ], + ); + } + Widget buildLinkTitle() { if (existingLink != null) { if (existingLink!.type == SharedLinkSource.album) { - return Row( - children: [ - const Text('public_album', style: TextStyle(fontWeight: FontWeight.bold)).tr(), - const Text(" | ", style: TextStyle(fontWeight: FontWeight.bold)), - Text( - existingLink!.title, - style: TextStyle(color: colorScheme.primary, fontWeight: FontWeight.bold), - ), - ], - ); + return buildSharedLinkRow(leading: context.t.public_album, content: existingLink!.title); } if (existingLink!.type == SharedLinkSource.individual) { - return Row( - children: [ - const Text('shared_link_individual_shared', style: TextStyle(fontWeight: FontWeight.bold)).tr(), - const Text(" | ", style: TextStyle(fontWeight: FontWeight.bold)), - Expanded( - child: Text( - existingLink!.description ?? "--", - style: TextStyle(color: colorScheme.primary, fontWeight: FontWeight.bold), - overflow: TextOverflow.ellipsis, - ), - ), - ], + return buildSharedLinkRow( + leading: context.t.shared_link_individual_shared, + content: existingLink!.description ?? "--", ); } } - return const Text("create_link_to_share_description", style: TextStyle(fontWeight: FontWeight.bold)).tr(); + return Text(context.t.create_link_to_share_description, style: const TextStyle(fontWeight: FontWeight.bold)); } Widget buildDescriptionField() { return TextField( controller: descriptionController, - enabled: newShareLink.value.isEmpty, focusNode: descriptionFocusNode, textInputAction: TextInputAction.done, autofocus: false, decoration: InputDecoration( - labelText: 'description'.tr(), - labelStyle: TextStyle(fontWeight: FontWeight.bold, color: colorScheme.primary), + labelText: context.t.description, + labelStyle: const TextStyle(fontWeight: FontWeight.bold), floatingLabelBehavior: FloatingLabelBehavior.always, border: const OutlineInputBorder(), - hintText: 'shared_link_edit_description_hint'.tr(), + hintText: context.t.shared_link_edit_description_hint, hintStyle: const TextStyle(fontWeight: FontWeight.normal, fontSize: 14), - disabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey.withValues(alpha: 0.5))), ), onTapOutside: (_) => descriptionFocusNode.unfocus(), ); @@ -96,16 +112,14 @@ class SharedLinkEditPage extends HookConsumerWidget { Widget buildPasswordField() { return TextField( controller: passwordController, - enabled: newShareLink.value.isEmpty, autofocus: false, decoration: InputDecoration( - labelText: 'password'.tr(), - labelStyle: TextStyle(fontWeight: FontWeight.bold, color: colorScheme.primary), + labelText: context.t.password, + labelStyle: const TextStyle(fontWeight: FontWeight.bold), floatingLabelBehavior: FloatingLabelBehavior.always, border: const OutlineInputBorder(), - hintText: 'shared_link_edit_password_hint'.tr(), + hintText: context.t.shared_link_edit_password_hint, hintStyle: const TextStyle(fontWeight: FontWeight.normal, fontSize: 14), - disabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey.withValues(alpha: 0.5))), ), ); } @@ -113,18 +127,16 @@ class SharedLinkEditPage extends HookConsumerWidget { Widget buildSlugField() { return TextField( controller: slugController, - enabled: newShareLink.value.isEmpty, focusNode: slugFocusNode, textInputAction: TextInputAction.done, autofocus: false, decoration: InputDecoration( - labelText: 'custom_url'.tr(), - labelStyle: TextStyle(fontWeight: FontWeight.bold, color: colorScheme.primary), - floatingLabelBehavior: FloatingLabelBehavior.always, + labelText: slugController.text.isNotEmpty ? context.t.custom_url : null, + labelStyle: const TextStyle(fontWeight: FontWeight.bold), border: const OutlineInputBorder(), - hintText: 'custom_url'.tr(), - hintStyle: const TextStyle(fontWeight: FontWeight.normal, fontSize: 14), - disabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey.withValues(alpha: 0.5))), + hintText: context.t.custom_url, + prefixText: slugController.text.isNotEmpty ? '/s/' : null, + prefixStyle: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14), ), onTapOutside: (_) => slugFocusNode.unfocus(), ); @@ -133,145 +145,182 @@ class SharedLinkEditPage extends HookConsumerWidget { Widget buildShowMetaButton() { return SwitchListTile.adaptive( value: showMetadata.value, - onChanged: newShareLink.value.isEmpty ? (value) => showMetadata.value = value : null, - activeThumbColor: colorScheme.primary, + onChanged: (value) => showMetadata.value = value, dense: true, - title: Text("show_metadata", style: themeData.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.bold)).tr(), + title: Text( + context.t.show_metadata, + style: themeData.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.bold), + ), ); } Widget buildAllowDownloadButton() { return SwitchListTile.adaptive( value: allowDownload.value, - onChanged: newShareLink.value.isEmpty ? (value) => allowDownload.value = value : null, - activeThumbColor: colorScheme.primary, + onChanged: (value) => allowDownload.value = value, dense: true, title: Text( - "allow_public_user_to_download", + context.t.allow_public_user_to_download, style: themeData.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.bold), - ).tr(), + ), ); } Widget buildAllowUploadButton() { return SwitchListTile.adaptive( value: allowUpload.value, - onChanged: newShareLink.value.isEmpty ? (value) => allowUpload.value = value : null, - activeThumbColor: colorScheme.primary, + onChanged: (value) => allowUpload.value = value, dense: true, title: Text( - "allow_public_user_to_upload", + context.t.allow_public_user_to_upload, style: themeData.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.bold), - ).tr(), + ), ); } - Widget buildEditExpiryButton() { - return SwitchListTile.adaptive( - value: editExpiry.value, - onChanged: newShareLink.value.isEmpty ? (value) => editExpiry.value = value : null, - activeThumbColor: colorScheme.primary, - dense: true, - title: Text( - "change_expiration_time", - style: themeData.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.bold), - ).tr(), + String formatDateTime(DateTime dateTime) => DateFormat.yMMMd(context.locale.toString()).add_Hm().format(dateTime); + + DateTime? getExpiresAtFromPreset(Duration preset) => preset == Duration.zero ? null : DateTime.now().add(preset); + + Future selectDate() async { + final today = DateTime.now(); + final safeInitialDate = expiryAfter.value ?? today.add(const Duration(days: 7)); + final initialDate = safeInitialDate.isBefore(today) ? today : safeInitialDate; + + final selectedDate = await showDatePicker( + context: context, + initialDate: initialDate, + firstDate: today, + lastDate: today.add(const Duration(days: maxFutureDate)), ); + + if (selectedDate != null && context.mounted) { + final isToday = + selectedDate.year == today.year && selectedDate.month == today.month && selectedDate.day == today.day; + final initialTime = isToday ? TimeOfDay.fromDateTime(today) : const TimeOfDay(hour: 12, minute: 0); + + final selectedTime = await showTimePicker(context: context, initialTime: initialTime); + + if (selectedTime != null) { + final now = DateTime.now(); + var finalDateTime = DateTime( + selectedDate.year, + selectedDate.month, + selectedDate.day, + selectedTime.hour, + selectedTime.minute, + ); + + if (finalDateTime.isBefore(now) && isToday) { + finalDateTime = now; + } + + selectedPresetIndex.value = null; + expiryAfter.value = finalDateTime; + } + } } Widget buildExpiryAfterButton() { - return DropdownMenu( - label: Text( - "expire_after", - style: TextStyle(fontWeight: FontWeight.bold, color: colorScheme.primary), - ).tr(), - enableSearch: false, - enableFilter: false, - width: context.width - 40, - initialSelection: expiryAfter.value, - enabled: newShareLink.value.isEmpty && (existingLink == null || editExpiry.value), - onSelected: (value) { - expiryAfter.value = value!; - }, - dropdownMenuEntries: [ - DropdownMenuEntry(value: 0, label: "never".tr()), - DropdownMenuEntry( - value: 30, - label: "shared_link_edit_expire_after_option_minutes".tr(namedArgs: {'count': "30"}), - ), - DropdownMenuEntry(value: 60, label: "shared_link_edit_expire_after_option_hour".tr()), - DropdownMenuEntry( - value: 60 * 6, - label: "shared_link_edit_expire_after_option_hours".tr(namedArgs: {'count': "6"}), - ), - DropdownMenuEntry(value: 60 * 24, label: "shared_link_edit_expire_after_option_day".tr()), - DropdownMenuEntry( - value: 60 * 24 * 7, - label: "shared_link_edit_expire_after_option_days".tr(namedArgs: {'count': "7"}), - ), - DropdownMenuEntry( - value: 60 * 24 * 30, - label: "shared_link_edit_expire_after_option_days".tr(namedArgs: {'count': "30"}), - ), - DropdownMenuEntry( - value: 60 * 24 * 30 * 3, - label: "shared_link_edit_expire_after_option_months".tr(namedArgs: {'count': "3"}), - ), - DropdownMenuEntry( - value: 60 * 24 * 30 * 12, - label: "shared_link_edit_expire_after_option_year".tr(namedArgs: {'count': "1"}), - ), - ], - ); - } - - void copyLinkToClipboard() { - Clipboard.setData(ClipboardData(text: newShareLink.value)).then((_) { - context.scaffoldMessenger.showSnackBar( - SnackBar( - content: Text( - "shared_link_clipboard_copied_massage", - style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor), - ).tr(), - duration: const Duration(seconds: 2), - ), - ); - }); - } - - Widget buildNewLinkField() { - return Column( + return ExpansionTile( + title: Text( + context.t.expire_after, + style: themeData.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.bold), + ), + subtitle: Text( + expiryAfter.value == null ? context.t.shared_link_expires_never : formatDateTime(expiryAfter.value!), + style: TextStyle(color: themeData.colorScheme.primary), + ), children: [ - const Padding(padding: EdgeInsets.only(top: 20, bottom: 20), child: Divider()), - TextFormField( - readOnly: true, - initialValue: newShareLink.value, - decoration: InputDecoration( - border: const OutlineInputBorder(), - enabledBorder: themeData.inputDecorationTheme.focusedBorder, - suffixIcon: IconButton(onPressed: copyLinkToClipboard, icon: const Icon(Icons.copy)), - ), - ), Padding( - padding: const EdgeInsets.only(top: 16.0), - child: Align( - alignment: Alignment.bottomRight, - child: ElevatedButton( - onPressed: () { - context.maybePop(); - }, - child: const Text("done", style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold)).tr(), - ), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Wrap( + spacing: 8, + runSpacing: 8, + children: List.generate(expiryPresets.length, (index) { + final preset = expiryPresets[index]; + return ChoiceChip( + label: Text(preset.$2), + selected: selectedPresetIndex.value == index, + onSelected: (_) { + selectedPresetIndex.value = index; + expiryAfter.value = getExpiresAtFromPreset(preset.$1); + }, + ); + }), + ), + if (expiryAfter.value != null) ...[ + const SizedBox(height: 16), + SizedBox( + width: double.infinity, + child: OutlinedButton.icon( + onPressed: selectDate, + icon: const Icon(Icons.edit_calendar), + label: Text(context.t.edit_date_and_time), + ), + ), + ], + ], ), ), ], ); } - DateTime calculateExpiry() { - return DateTime.now().add(Duration(minutes: expiryAfter.value)); + Future copyToClipboard(String link) async { + await Clipboard.setData(ClipboardData(text: link)); + if (!context.mounted) { + return; + } + context.scaffoldMessenger.showSnackBar( + SnackBar( + content: Text( + context.t.shared_link_clipboard_copied_massage, + style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor), + ), + duration: const Duration(seconds: 2), + ), + ); } + Widget buildLinkCopyField(String link) { + return TextFormField( + readOnly: true, + onTap: () => copyToClipboard(link), + initialValue: link, + decoration: InputDecoration( + border: const OutlineInputBorder(), + enabledBorder: themeData.inputDecorationTheme.focusedBorder, + suffixIcon: IconButton(onPressed: () => Share.share(link), icon: const Icon(Icons.share)), + ), + ); + } + + Widget buildNewLinkReadyScreen() { + return Padding( + padding: const EdgeInsets.all(20), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.add_link, size: 100, color: themeData.colorScheme.primary), + const SizedBox(height: 20), + buildLinkCopyField(newShareLink.value), + const SizedBox(height: 20), + ElevatedButton.icon( + onPressed: () => context.maybePop(), + icon: const Icon(Icons.check), + label: Text(context.t.done, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)), + ), + ], + ), + ); + } + + DateTime? calculateExpiry() => expiryAfter.value; + Future handleNewLink() async { final newLink = await ref .read(sharedLinkServiceProvider) @@ -284,30 +333,30 @@ class SharedLinkEditPage extends HookConsumerWidget { description: descriptionController.text.isEmpty ? null : descriptionController.text, password: passwordController.text.isEmpty ? null : passwordController.text, slug: slugController.text.isEmpty ? null : slugController.text, - expiresAt: expiryAfter.value == 0 ? null : calculateExpiry(), + expiresAt: calculateExpiry()?.toUtc(), ); + if (!context.mounted) { + return; + } ref.invalidate(sharedLinksStateProvider); await ref.read(serverInfoProvider.notifier).getServerConfig(); + if (!context.mounted) { + return; + } final externalDomain = ref.read(serverInfoProvider.select((s) => s.serverConfig.externalDomain)); - var serverUrl = externalDomain.isNotEmpty ? externalDomain : getServerUrl(); - if (serverUrl != null && !serverUrl.endsWith('/')) { - serverUrl += '/'; - } + final serverUrl = externalDomain.isNotEmpty ? externalDomain : getServerUrl(); - if (newLink != null && serverUrl != null) { - final hasSlug = newLink.slug?.isNotEmpty == true; - final urlPath = hasSlug ? newLink.slug : newLink.key; - final basePath = hasSlug ? 's' : 'share'; - newShareLink.value = "$serverUrl$basePath/$urlPath"; - copyLinkToClipboard(); - } else if (newLink == null) { + if (newLink != null) { + newShareLink.value = buildSharedLinkUrl(baseUrl: serverUrl, slug: newLink.slug, key: newLink.key) ?? ''; + await copyToClipboard(newShareLink.value); + } else { ImmichToast.show( context: context, gravity: ToastGravity.BOTTOM, toastType: ToastType.error, - msg: 'shared_link_create_error'.tr(), + msg: context.t.shared_link_create_error, ); } } @@ -348,8 +397,9 @@ class SharedLinkEditPage extends HookConsumerWidget { slug = existingLink!.slug; } - if (editExpiry.value) { - expiry = expiryAfter.value == 0 ? null : calculateExpiry(); + final newExpiry = expiryAfter.value; + if (newExpiry?.toUtc() != existingLink!.expiresAt?.toUtc()) { + expiry = newExpiry; changeExpiry = true; } @@ -363,69 +413,115 @@ class SharedLinkEditPage extends HookConsumerWidget { description: desc, password: password, slug: slug, - expiresAt: expiry, + expiresAt: expiry?.toUtc(), changeExpiry: changeExpiry, ); + if (!context.mounted) { + return; + } ref.invalidate(sharedLinksStateProvider); await context.maybePop(); } + Future handleDeleteLink() async { + return showDialog( + context: context, + builder: (BuildContext context) => ConfirmDialog( + title: "delete_shared_link_dialog_title", + content: "confirm_delete_shared_link", + onOk: () async { + await ref.read(sharedLinkServiceProvider).deleteSharedLink(existingLink!.id); + ref.invalidate(sharedLinksStateProvider); + if (context.mounted) { + await context.maybePop(); + } + }, + ), + ); + } + return Scaffold( appBar: AppBar( - title: Text(existingLink == null ? "create_link_to_share" : "edit_link").tr(), + title: Text(existingLink == null ? context.t.create_link_to_share : context.t.edit_link), elevation: 0, leading: const CloseButton(), centerTitle: false, ), body: SafeArea( - child: ListView( - children: [ - Padding(padding: const EdgeInsets.all(padding), child: buildLinkTitle()), - Padding(padding: const EdgeInsets.all(padding), child: buildDescriptionField()), - Padding(padding: const EdgeInsets.all(padding), child: buildPasswordField()), - Padding(padding: const EdgeInsets.all(padding), child: buildSlugField()), - Padding( - padding: const EdgeInsets.only(left: padding, right: padding, bottom: padding), - child: buildShowMetaButton(), - ), - Padding( - padding: const EdgeInsets.only(left: padding, right: padding, bottom: padding), - child: buildAllowDownloadButton(), - ), - Padding( - padding: const EdgeInsets.only(left: padding, right: 20, bottom: 20), - child: buildAllowUploadButton(), - ), - if (existingLink != null) - Padding( - padding: const EdgeInsets.only(left: padding, right: padding, bottom: padding), - child: buildEditExpiryButton(), - ), - Padding( - padding: const EdgeInsets.only(left: padding, right: padding, bottom: padding), - child: buildExpiryAfterButton(), - ), - if (newShareLink.value.isEmpty) - Align( - alignment: Alignment.bottomRight, - child: Padding( - padding: const EdgeInsets.only(right: padding + 10, bottom: padding), - child: ElevatedButton( - onPressed: existingLink != null ? handleEditLink : handleNewLink, - child: Text( - existingLink != null ? "shared_link_edit_submit_button" : "create_link", - style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold), - ).tr(), - ), + child: newShareLink.value.isEmpty + ? Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: ListView( + children: [ + const SizedBox(height: 20), + buildLinkTitle(), + if (existingLink != null) + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 16), + buildLinkCopyField( + buildSharedLinkUrl( + baseUrl: displayServerUrl, + slug: existingLink!.slug, + key: existingLink!.key, + ) ?? + '', + ), + const SizedBox(height: 24), + const Divider(), + ], + ), + const SizedBox(height: 24), + buildDescriptionField(), + const SizedBox(height: 16), + buildPasswordField(), + const SizedBox(height: 16), + buildSlugField(), + const SizedBox(height: 16), + buildShowMetaButton(), + const SizedBox(height: 16), + buildAllowDownloadButton(), + const SizedBox(height: 16), + buildAllowUploadButton(), + const SizedBox(height: 16), + buildExpiryAfterButton(), + const SizedBox(height: 24), + Align( + alignment: Alignment.centerRight, + child: Row( + mainAxisSize: MainAxisSize.min, + spacing: 8, + children: [ + if (existingLink != null) + OutlinedButton.icon( + style: OutlinedButton.styleFrom( + foregroundColor: themeData.colorScheme.error, + side: BorderSide(color: themeData.colorScheme.error), + ), + onPressed: handleDeleteLink, + icon: const Icon(Icons.delete_outline), + label: Text( + context.t.delete, + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold), + ), + ), + ElevatedButton.icon( + icon: const Icon(Icons.check), + onPressed: existingLink != null ? handleEditLink : handleNewLink, + label: Text( + existingLink != null ? context.t.shared_link_edit_submit_button : context.t.create_link, + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold), + ), + ), + ], + ), + ), + const SizedBox(height: 40), + ], ), - ), - if (newShareLink.value.isNotEmpty) - Padding( - padding: const EdgeInsets.only(left: padding, right: padding, bottom: padding), - child: buildNewLinkField(), - ), - ], - ), + ) + : Center(child: buildNewLinkReadyScreen()), ), ); } diff --git a/mobile/lib/platform/native_sync_api.g.dart b/mobile/lib/platform/native_sync_api.g.dart index 93e66455c2..ffd7dc0694 100644 --- a/mobile/lib/platform/native_sync_api.g.dart +++ b/mobile/lib/platform/native_sync_api.g.dart @@ -673,6 +673,25 @@ class NativeSyncApi { return (pigeonVar_replyValue! as Map).cast>(); } + Future restoreFromTrashById(String mediaId, int type) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.restoreFromTrashById$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([mediaId, type]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as bool; + } + Future> getCloudIdForAssetIds(List assetIds) async { final pigeonVar_channelName = 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds$pigeonVar_messageChannelSuffix'; diff --git a/mobile/lib/platform/network_api.g.dart b/mobile/lib/platform/network_api.g.dart index 7fab476694..6258060bfb 100644 --- a/mobile/lib/platform/network_api.g.dart +++ b/mobile/lib/platform/network_api.g.dart @@ -309,4 +309,23 @@ class NetworkApi { _extractReplyValueOrThrow(pigeonVar_replyList, pigeonVar_channelName, isNullValid: true); } + + Future getAppGroupId() async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.NetworkApi.getAppGroupId$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as String; + } } diff --git a/mobile/lib/platform/permission_api.g.dart b/mobile/lib/platform/permission_api.g.dart new file mode 100644 index 0000000000..d2646e482f --- /dev/null +++ b/mobile/lib/platform/permission_api.g.dart @@ -0,0 +1,119 @@ +// Autogenerated from Pigeon (v26.3.4), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: unused_import, unused_shown_name +// ignore_for_file: type=lint + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List; + +import 'package:flutter/services.dart'; +import 'package:meta/meta.dart' show immutable, protected, visibleForTesting; + +Object? _extractReplyValueOrThrow(List? replyList, String channelName, {required bool isNullValid}) { + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); + } else if (replyList.length > 1) { + throw PlatformException(code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2]); + } else if (!isNullValid && (replyList.isNotEmpty && replyList[0] == null)) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } + return replyList.firstOrNull; +} + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is int) { + buffer.putUint8(4); + buffer.putInt64(value); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + default: + return super.readValueOfType(type, buffer); + } + } +} + +class PermissionApi { + /// Constructor for [PermissionApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + PermissionApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; + + Future hasManageMediaPermission() async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.PermissionApi.hasManageMediaPermission$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as bool; + } + + Future requestManageMediaPermission() async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.PermissionApi.requestManageMediaPermission$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as bool; + } + + Future manageMediaPermission() async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.PermissionApi.manageMediaPermission$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as bool; + } +} diff --git a/mobile/lib/presentation/pages/drift_album.page.dart b/mobile/lib/presentation/pages/drift_album.page.dart index c9fed636b4..47a4625f87 100644 --- a/mobile/lib/presentation/pages/drift_album.page.dart +++ b/mobile/lib/presentation/pages/drift_album.page.dart @@ -37,6 +37,7 @@ class _DriftAlbumsPageState extends ConsumerState { final scrollView = CustomScrollView( controller: _scrollController, + physics: const AlwaysScrollableScrollPhysics(), slivers: [ ImmichSliverAppBar( snap: false, diff --git a/mobile/lib/presentation/pages/drift_asset_selection_timeline.page.dart b/mobile/lib/presentation/pages/drift_asset_selection_timeline.page.dart index 19f813cdb5..a12ca4932b 100644 --- a/mobile/lib/presentation/pages/drift_asset_selection_timeline.page.dart +++ b/mobile/lib/presentation/pages/drift_asset_selection_timeline.page.dart @@ -5,7 +5,6 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; -import 'package:immich_mobile/providers/user.provider.dart'; @RoutePage() class DriftAssetSelectionTimelinePage extends ConsumerWidget { @@ -22,17 +21,13 @@ class DriftAssetSelectionTimelinePage extends ConsumerWidget { ), ), timelineServiceProvider.overrideWith((ref) { - final user = ref.watch(currentUserProvider); - if (user == null) { - throw Exception('User must be logged in to access asset selection timeline'); - } - - final timelineService = ref.watch(timelineFactoryProvider).remoteAssets(user.id); + final timelineUsers = ref.watch(timelineUsersProvider).valueOrNull ?? []; + final timelineService = ref.watch(timelineFactoryProvider).main(timelineUsers); ref.onDispose(timelineService.dispose); return timelineService; }), ], - child: const Timeline(), + child: const Timeline(showStorageIndicator: true), ); } } diff --git a/mobile/lib/presentation/pages/drift_create_album.page.dart b/mobile/lib/presentation/pages/drift_create_album.page.dart index f1cbdb13ff..e6ff10fd59 100644 --- a/mobile/lib/presentation/pages/drift_create_album.page.dart +++ b/mobile/lib/presentation/pages/drift_create_album.page.dart @@ -179,17 +179,14 @@ class _DriftCreateAlbumPageState extends ConsumerState { } final album = await ref - .watch(remoteAlbumProvider.notifier) - .createAlbum( + .read(remoteAlbumProvider.notifier) + .createAlbumWithAssets( title: title, description: albumDescriptionController.text.trim(), - assetIds: selectedAssets.map((asset) { - final remoteAsset = asset as RemoteAsset; - return remoteAsset.id; - }).toList(), + assets: selectedAssets, ); - if (album != null) { + if (album != null && context.mounted) { unawaited(context.replaceRoute(RemoteAlbumRoute(album: album))); } } diff --git a/mobile/lib/presentation/pages/drift_remote_album.page.dart b/mobile/lib/presentation/pages/drift_remote_album.page.dart index 09c7912bc6..ccbddb99f3 100644 --- a/mobile/lib/presentation/pages/drift_remote_album.page.dart +++ b/mobile/lib/presentation/pages/drift_remote_album.page.dart @@ -8,6 +8,7 @@ import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/album/pending_uploads_banner.widget.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/remote_album_bottom_sheet.widget.dart'; import 'package:immich_mobile/presentation/widgets/remote_album/drift_album_option.widget.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; @@ -39,7 +40,8 @@ class _RemoteAlbumPageState extends ConsumerState { } Future addAssets(BuildContext context) async { - final albumAssets = await ref.read(remoteAlbumProvider.notifier).getAssets(_album.id); + final notifier = ref.read(remoteAlbumProvider.notifier); + final albumAssets = await notifier.getAssets(_album.id); final newAssets = await context.pushRoute>( DriftAssetSelectionTimelineRoute(lockedSelectionAssets: albumAssets.toSet()), @@ -49,17 +51,9 @@ class _RemoteAlbumPageState extends ConsumerState { return; } - final added = await ref - .read(remoteAlbumProvider.notifier) - .addAssets( - _album.id, - newAssets.map((asset) { - final remoteAsset = asset as RemoteAsset; - return remoteAsset.id; - }).toList(), - ); + final added = await notifier.addAssetsToAlbum(_album.id, newAssets); - if (added > 0) { + if (added > 0 && context.mounted) { ImmichToast.show( context: context, msg: "assets_added_to_album_count".t(context: context, args: {'count': added.toString()}), @@ -186,6 +180,7 @@ class _RemoteAlbumPageState extends ConsumerState { currentRemoteAlbumScopedProvider.overrideWithValue(_album), ], child: Timeline( + topSliverWidget: PendingUploadsBanner(albumId: _album.id), appBar: RemoteAlbumSliverAppBar( icon: Icons.photo_album_outlined, kebabMenu: _AlbumKebabMenu( diff --git a/mobile/lib/presentation/pages/drift_slideshow.page.dart b/mobile/lib/presentation/pages/drift_slideshow.page.dart new file mode 100644 index 0000000000..4c4ee48cf9 --- /dev/null +++ b/mobile/lib/presentation/pages/drift_slideshow.page.dart @@ -0,0 +1,376 @@ +import 'dart:async'; +import 'dart:math'; +import 'dart:ui'; + +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/config/slideshow_config.dart'; +import 'package:immich_mobile/domain/services/timeline.service.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/scroll_extensions.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/pages/common/settings.page.dart'; +import 'package:immich_mobile/presentation/widgets/asset_viewer/video_viewer.widget.dart'; +import 'package:immich_mobile/presentation/widgets/images/image_provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart'; +import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart'; +import 'package:immich_mobile/widgets/photo_view/photo_view.dart'; +import 'package:wakelock_plus/wakelock_plus.dart'; + +@RoutePage() +class DriftSlideshowPage extends ConsumerStatefulWidget { + final TimelineService timeline; + + const DriftSlideshowPage({super.key, required this.timeline}); + + @override + ConsumerState createState() => _DriftSlideshowPageState(); +} + +class _DriftSlideshowPageState extends ConsumerState { + late SlideshowConfig _config; + late final PageController _pageController; + late final Stopwatch _stopwatch; + late Timer _timer; + late int _index; + late int _nextIndex; + bool _paused = false; + bool _showAppBar = false; + + @override + initState() { + super.initState(); + _config = ref.read(appConfigProvider.select((s) => s.slideshow)); + final asset = ref.read(assetViewerProvider).currentAsset; + _index = asset == null ? 0 : widget.timeline.getIndex(asset.heroTag) ?? 0; + _pageController = PageController(initialPage: _index); + _stopwatch = Stopwatch(); + _createTimer(); + _updateNextIndex(); + ref.listenManual(appConfigProvider.select((s) => s.slideshow), _onConfigChanged); + + SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); + unawaited(WakelockPlus.enable()); + } + + @override + dispose() { + _timer.cancel(); + _stopwatch.stop(); + _pageController.dispose(); + unawaited(WakelockPlus.disable()); + SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); + super.dispose(); + } + + void _play() { + final asset = widget.timeline.getAssetSafe(_index)!; + + if (asset.isImage) { + _createTimer(); + } else if (ref.read(videoPlayerProvider(asset.heroTag)).status == VideoPlaybackStatus.paused) { + ref.read(videoPlayerProvider(asset.heroTag).notifier).play(); + } else { + _nextPage(); + } + + _updateNextIndex(); + + setState(() { + _paused = false; + }); + } + + void _pause() { + _timer.cancel(); + _stopwatch.stop(); + + final asset = widget.timeline.getAssetSafe(_index)!; + + if (!asset.isImage) { + ref.read(videoPlayerProvider(asset.heroTag).notifier).pause(); + } + + setState(() { + _paused = true; + }); + } + + void _onConfigChanged(SlideshowConfig? previous, SlideshowConfig next) { + if (_config == next) { + return; + } + + final durationChanged = _config.duration != next.duration; + _config = next; + _updateNextIndex(); + + final asset = widget.timeline.getAssetSafe(_index); + if (durationChanged && !_paused && asset?.isImage == true) { + _timer.cancel(); + _createTimer(); + } + + setState(() {}); + } + + void _updateNextIndex() { + _nextIndex = switch (_config.direction) { + SlideshowDirection.forward => _index + 1, + SlideshowDirection.backward => _index - 1, + SlideshowDirection.shuffle => widget.timeline.getIndex(widget.timeline.getRandomAsset().heroTag)!, + }; + + if (!widget.timeline.hasRange(_nextIndex, 1)) { + widget.timeline.preloadAssets(_nextIndex); + } + } + + void _nextPage() async { + if (_nextIndex < 0 || _nextIndex >= widget.timeline.totalAssets) { + if (_config.repeat) { + final wrapped = _config.direction == SlideshowDirection.forward ? 0 : widget.timeline.totalAssets - 1; + await widget.timeline.preloadAssets(wrapped); + _pageController.jumpToPage(wrapped); + } else { + setState(() { + _paused = true; + }); + } + return; + } + + if (!widget.timeline.hasRange(_nextIndex, 1)) { + await widget.timeline.preloadAssets(_nextIndex); + } + + if (_config.direction == SlideshowDirection.shuffle || !_config.transition) { + _pageController.jumpToPage(_nextIndex); + } else { + unawaited(_pageController.animateToPage(_nextIndex, duration: Durations.long2, curve: Curves.easeIn)); + } + } + + void _createTimer() { + _timer = Timer(Duration(milliseconds: _config.duration * 1000 - _stopwatch.elapsedMilliseconds), () { + _stopwatch.stop(); + _stopwatch.reset(); + _nextPage(); + }); + + _stopwatch.start(); + } + + void _pageChanged(int page) { + final asset = widget.timeline.getAssetSafe(page)!; + + setState(() { + _index = page; + + if (!asset.isImage) { + _paused = false; + } + }); + + _timer.cancel(); + _stopwatch.stop(); + _stopwatch.reset(); + + if (!_paused && asset.isImage) { + _createTimer(); + } + + _updateNextIndex(); + } + + void _onTapUp() async { + await SystemChrome.setEnabledSystemUIMode(_showAppBar ? SystemUiMode.immersive : SystemUiMode.edgeToEdge); + + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + _showAppBar = !_showAppBar; + }); + }); + } + + Widget _getProgressBar(BuildContext context) { + final asset = widget.timeline.getAssetSafe(_index); + + if (asset == null) { + return Container(); + } + + if (asset.isImage) { + final elapsed = _stopwatch.elapsedMilliseconds; + final duration = _config.duration * 1000; + + return TweenAnimationBuilder( + key: Key(_index.toString()), + tween: Tween(begin: elapsed / duration.toDouble(), end: _paused ? elapsed / duration.toDouble() : 1.0), + duration: Duration(milliseconds: _paused ? 1 : max(duration - elapsed, 1)), + builder: (context, value, _) => LinearProgressIndicator( + color: context.colorScheme.primary, + borderRadius: const BorderRadius.all(Radius.zero), + minHeight: 5, + value: value, + ), + ); + } else { + return LinearProgressIndicator( + color: context.colorScheme.primary, + borderRadius: const BorderRadius.all(Radius.zero), + minHeight: 5, + value: + ref.watch(videoPlayerProvider(asset.heroTag).select((s) => s.position)).inMilliseconds / + asset.duration.inMilliseconds, + ); + } + } + + Widget _getBlur(BuildContext context, int index) { + final asset = widget.timeline.getAssetSafe(index); + + if (asset == null) { + return Container(); + } + + return ImageFiltered( + imageFilter: ImageFilter.blur(sigmaX: 30, sigmaY: 30), + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: getFullImageProvider(asset, size: Size(context.width, context.height)), + fit: BoxFit.cover, + ), + ), + child: Container(color: Colors.black.withValues(alpha: 0.2)), + ), + ); + } + + Widget _getPhotoView(BuildContext context, int index) { + final asset = widget.timeline.getAssetSafe(index); + + if (asset == null) { + return const Center(child: ImmichLoadingIndicator()); + } + + final scale = _config.look == SlideshowLook.cover + ? PhotoViewComputedScale.covered + : PhotoViewComputedScale.contained; + final isCurrent = _index == index; + final imageProvider = getFullImageProvider(asset, size: context.sizeData); + + if (asset.isImage) { + final zoomOut = index % 2 == 1; + final elapsed = _stopwatch.elapsedMilliseconds; + final duration = _config.duration * 1000; + final progress = zoomOut ? 1.0 - elapsed / duration.toDouble() : elapsed / duration.toDouble(); + + return TweenAnimationBuilder( + tween: Tween( + begin: progress, + end: _paused + ? progress + : zoomOut + ? 0.0 + : 1.0, + ), + duration: Duration(milliseconds: _paused ? 1 : max(duration - elapsed, 1)), + builder: (context, value, _) => PhotoView( + imageProvider: imageProvider, + index: index, + disableScaleGestures: true, + gaplessPlayback: true, + filterQuality: FilterQuality.high, + initialScale: scale * (1.0 + value / 10.0), + controller: PhotoViewController(), + onTapUp: (_, _, _) => _onTapUp(), + ), + ); + } else { + final status = ref.watch(videoPlayerProvider(asset.heroTag).select((s) => s.status)); + final position = ref.read(videoPlayerProvider(asset.heroTag)).position; + + if (status == VideoPlaybackStatus.completed && isCurrent && position.inMicroseconds > 0) { + _nextPage(); + } else if (status == VideoPlaybackStatus.playing) { + ref.read(videoPlayerProvider(asset.heroTag).notifier).setLoop(false); + } + + return PhotoView.customChild( + onTapUp: (_, _, _) => _onTapUp(), + disableScaleGestures: true, + filterQuality: FilterQuality.high, + initialScale: scale, + child: NativeVideoViewer( + asset: asset, + isCurrent: isCurrent, + image: Image(image: imageProvider, fit: BoxFit.contain, alignment: Alignment.center), + ), + ); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: PreferredSize( + preferredSize: Size(AppBar().preferredSize.width, AppBar().preferredSize.height + 5), + child: IgnorePointer( + ignoring: !_showAppBar, + child: AnimatedOpacity( + opacity: _showAppBar ? 1.0 : 0.0, + duration: Durations.short2, + child: Column( + children: [ + AppBar( + backgroundColor: context.scaffoldBackgroundColor, + title: Text("slideshow".t(context: context)), + actions: [ + IconButton( + onPressed: _paused ? _play : _pause, + icon: Icon(_paused ? Icons.play_arrow : Icons.pause), + ), + IconButton( + onPressed: () { + _pause(); + context.pushRoute(SettingsSubRoute(section: SettingSection.assetViewer)); + }, + icon: const Icon(Icons.settings), + ), + ], + ), + _getProgressBar(context), + ], + ), + ), + ), + ), + extendBody: true, + extendBodyBehindAppBar: true, + backgroundColor: Colors.black, + body: PhotoViewGestureDetectorScope( + axis: Axis.horizontal, + child: PageView.builder( + controller: _pageController, + physics: const FastClampingScrollPhysics(), + itemCount: widget.timeline.totalAssets, + onPageChanged: _pageChanged, + itemBuilder: (context, index) => Stack( + children: [ + if (_config.look == SlideshowLook.blurredBackground) _getBlur(context, index), + _getPhotoView(context, index), + ], + ), + ), + ), + ); + } +} diff --git a/mobile/lib/presentation/pages/search/drift_search.page.dart b/mobile/lib/presentation/pages/search/drift_search.page.dart index 7b747738dd..2a40556641 100644 --- a/mobile/lib/presentation/pages/search/drift_search.page.dart +++ b/mobile/lib/presentation/pages/search/drift_search.page.dart @@ -186,7 +186,7 @@ class DriftSearchPage extends HookConsumerWidget { expanded: true, onSearch: handleApply, onClear: handleClear, - child: TagPicker(onSelect: handleOnSelect, filter: (filter.value.tagIds ?? []).toSet()), + child: TagPicker(onSelectExistingTag: handleOnSelect, filter: (filter.value.tagIds ?? []).toSet()), ), ), ); diff --git a/mobile/lib/presentation/widgets/action_buttons/base_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/base_action_button.widget.dart index 1ca875e483..5ed61c3bbe 100644 --- a/mobile/lib/presentation/widgets/action_buttons/base_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/base_action_button.widget.dart @@ -35,10 +35,11 @@ class BaseActionButton extends ConsumerWidget { final miniWidth = minWidth ?? (context.isMobile ? context.width / 4.5 : 75.0); final iconTheme = IconTheme.of(context); final iconSize = iconTheme.size ?? 24.0; - final iconColor = this.iconColor ?? iconTheme.color ?? context.themeData.iconTheme.color; final textColor = context.themeData.textTheme.labelLarge?.color; if (iconOnly) { + final iconColor = this.iconColor ?? iconTheme.color ?? context.themeData.iconTheme.color; + return IconButton( onPressed: onPressed, icon: Icon(iconData, size: iconSize, color: iconColor), @@ -46,17 +47,21 @@ class BaseActionButton extends ConsumerWidget { } if (menuItem) { - final theme = context.themeData; - final effectiveIconColor = iconColor ?? theme.iconTheme.color ?? theme.colorScheme.onSurfaceVariant; + final iconColor = this.iconColor; return MenuItemButton( - style: MenuItemButton.styleFrom(alignment: Alignment.centerLeft, padding: const EdgeInsets.all(16)), - leadingIcon: Icon(iconData, color: effectiveIconColor), + style: MenuItemButton.styleFrom( + alignment: Alignment.centerLeft, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + ), + leadingIcon: Icon(iconData, color: iconColor, size: 20), onPressed: onPressed, - child: Text(label, style: theme.textTheme.labelLarge?.copyWith(fontSize: 16, color: iconColor)), + child: Text(label, style: TextStyle(fontSize: 15, color: iconColor)), ); } + final iconColor = this.iconColor ?? iconTheme.color ?? context.themeData.iconTheme.color; + return ConstrainedBox( constraints: BoxConstraints(maxWidth: maxWidth), child: MaterialButton( diff --git a/mobile/lib/presentation/widgets/action_buttons/bulk_tag_assets_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/bulk_tag_assets_action_button.widget.dart new file mode 100644 index 0000000000..b9ac47cd57 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/bulk_tag_assets_action_button.widget.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +class BulkTagAssetsActionButton extends ConsumerWidget { + final ActionSource source; + + const BulkTagAssetsActionButton({super.key, required this.source}); + + Future _onTap(BuildContext context, WidgetRef ref) async { + final result = await ref.read(actionProvider.notifier).tagAssets(source, context); + if (result == null) { + return; + } + + ref.read(multiSelectProvider.notifier).reset(); + + if (!context.mounted) { + return; + } + + ImmichToast.show( + context: context, + msg: result.success + ? 'tagged_assets'.t(context: context, args: {'count': result.count.toString()}) + : 'errors.failed_to_tag_assets'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BaseActionButton( + iconData: Icons.sell_outlined, + label: "control_bottom_app_bar_add_tags".t(context: context), + onPressed: () => _onTap(context, ref), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart index d2df013369..267a9f55e6 100644 --- a/mobile/lib/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart @@ -18,8 +18,15 @@ class DeletePermanentActionButton extends ConsumerWidget { final ActionSource source; final bool iconOnly; final bool menuItem; + final bool useShortLabel; - const DeletePermanentActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false}); + const DeletePermanentActionButton({ + super.key, + required this.source, + this.iconOnly = false, + this.menuItem = false, + this.useShortLabel = false, + }); void _onTap(BuildContext context, WidgetRef ref) async { if (!context.mounted) { @@ -64,7 +71,7 @@ class DeletePermanentActionButton extends ConsumerWidget { return BaseActionButton( maxWidth: 110.0, iconData: Icons.delete_forever, - label: "delete_permanently".t(context: context), + label: useShortLabel ? "delete".t(context: context) : "delete_permanently".t(context: context), iconOnly: iconOnly, menuItem: menuItem, onPressed: () => _onTap(context, ref), diff --git a/mobile/lib/presentation/widgets/action_buttons/slideshow_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/slideshow_action_button.widget.dart new file mode 100644 index 0000000000..479cf2dfe9 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/slideshow_action_button.widget.dart @@ -0,0 +1,34 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; + +class SlideshowActionButton extends ConsumerWidget { + final bool iconOnly; + final bool menuItem; + + const SlideshowActionButton({super.key, this.iconOnly = false, this.menuItem = false}); + + void _onTap(BuildContext context, WidgetRef ref) { + if (!context.mounted) { + return; + } + + context.pushRoute(DriftSlideshowRoute(timeline: ref.read(timelineServiceProvider))); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BaseActionButton( + iconData: Icons.slideshow, + label: "slideshow".t(context: context), + iconOnly: iconOnly, + menuItem: menuItem, + onPressed: () => _onTap(context, ref), + maxWidth: 100, + ); + } +} diff --git a/mobile/lib/presentation/widgets/album/album_selector.widget.dart b/mobile/lib/presentation/widgets/album/album_selector.widget.dart index c68a7273e0..6241623978 100644 --- a/mobile/lib/presentation/widgets/album/album_selector.widget.dart +++ b/mobile/lib/presentation/widgets/album/album_selector.widget.dart @@ -15,15 +15,15 @@ import 'package:immich_mobile/models/albums/album_search.model.dart'; import 'package:immich_mobile/presentation/widgets/album/album_tile.dart'; import 'package:immich_mobile/presentation/widgets/album/new_album_name_modal.widget.dart'; import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; +import 'package:immich_mobile/domain/models/metadata_key.dart'; import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/routing/router.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/utils/album_filter.utils.dart'; import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; @@ -58,19 +58,11 @@ class _AlbumSelectorState extends ConsumerState { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { - final appSettings = ref.read(appSettingsServiceProvider); - final savedSortMode = appSettings.getSetting(AppSettingsEnum.selectedAlbumSortOrder); - final savedIsReverse = appSettings.getSetting(AppSettingsEnum.selectedAlbumSortReverse); - final savedIsGrid = appSettings.getSetting(AppSettingsEnum.albumGridView); - - final albumSortMode = AlbumSortMode.values.firstWhere( - (e) => e.storeIndex == savedSortMode, - orElse: () => AlbumSortMode.lastModified, - ); + final albumConfig = ref.read(metadataProvider).appConfig.album; setState(() { - sort = AlbumSort(mode: albumSortMode, isReverse: savedIsReverse); - isGrid = savedIsGrid; + sort = AlbumSort(mode: albumConfig.sortMode, isReverse: albumConfig.isReverse); + isGrid = albumConfig.isGrid; }); ref.read(remoteAlbumProvider.notifier).refresh(); @@ -102,7 +94,7 @@ class _AlbumSelectorState extends ConsumerState { setState(() { isGrid = !isGrid; }); - ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.albumGridView, isGrid); + ref.read(metadataProvider).write(MetadataKey.albumIsGrid, isGrid); } void changeFilter(QuickFilterMode mode) { @@ -118,9 +110,9 @@ class _AlbumSelectorState extends ConsumerState { this.sort = sort; }); - final appSettings = ref.read(appSettingsServiceProvider); - await appSettings.setSetting(AppSettingsEnum.selectedAlbumSortOrder, sort.mode.storeIndex); - await appSettings.setSetting(AppSettingsEnum.selectedAlbumSortReverse, sort.isReverse); + final metadata = ref.read(metadataProvider); + await metadata.write(MetadataKey.albumSortMode, sort.mode); + await metadata.write(MetadataKey.albumIsReverse, sort.isReverse); await sortAlbums(); } diff --git a/mobile/lib/presentation/widgets/album/pending_uploads_banner.widget.dart b/mobile/lib/presentation/widgets/album/pending_uploads_banner.widget.dart new file mode 100644 index 0000000000..397170ec54 --- /dev/null +++ b/mobile/lib/presentation/widgets/album/pending_uploads_banner.widget.dart @@ -0,0 +1,252 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; +import 'package:immich_mobile/providers/album/pending_album_uploads.provider.dart'; + +/// Pinned banner sliver that surfaces in-flight album uploads directly under +/// the album app bar. Renders nothing while the queue is empty. Tapping the +/// banner opens a bottom sheet with per-asset progress. +class PendingUploadsBanner extends ConsumerWidget { + static const double _height = 52; + + final String albumId; + + const PendingUploadsBanner({super.key, required this.albumId}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final pending = ref.watch(pendingAlbumUploadsProvider(albumId)); + if (pending.isEmpty) { + return const SliverToBoxAdapter(child: SizedBox.shrink()); + } + + final hasFailures = pending.any((p) => p.failed); + final clamped = pending.map((p) => p.progress.clamp(0.0, 1.0)).toList(growable: false); + final overallProgress = clamped.isEmpty ? 0.0 : clamped.reduce((a, b) => a + b) / clamped.length; + final isIndeterminate = overallProgress <= 0.0; + + return SliverPersistentHeader( + pinned: true, + delegate: _PendingUploadsBannerDelegate( + height: _height, + child: _PendingUploadsBannerContent( + albumId: albumId, + previewAsset: pending.first.asset, + count: pending.length, + overallProgress: overallProgress, + isIndeterminate: isIndeterminate, + hasFailures: hasFailures, + ), + ), + ); + } + + static void _openSheet(BuildContext context, String albumId) { + showModalBottomSheet( + context: context, + showDragHandle: true, + builder: (_) => _PendingUploadsSheet(albumId: albumId), + ); + } +} + +class _PendingUploadsBannerDelegate extends SliverPersistentHeaderDelegate { + final double height; + final Widget child; + + const _PendingUploadsBannerDelegate({required this.height, required this.child}); + + @override + double get minExtent => height; + + @override + double get maxExtent => height; + + @override + Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) => child; + + @override + bool shouldRebuild(covariant _PendingUploadsBannerDelegate oldDelegate) => + height != oldDelegate.height || child != oldDelegate.child; +} + +class _PendingUploadsBannerContent extends StatelessWidget { + final String albumId; + final BaseAsset previewAsset; + final int count; + final double overallProgress; + final bool isIndeterminate; + final bool hasFailures; + + const _PendingUploadsBannerContent({ + required this.albumId, + required this.previewAsset, + required this.count, + required this.overallProgress, + required this.isIndeterminate, + required this.hasFailures, + }); + + @override + Widget build(BuildContext context) { + final percentLabel = isIndeterminate ? '' : ' · ${(overallProgress * 100).toInt()}%'; + return Material( + color: hasFailures ? context.colorScheme.errorContainer : context.colorScheme.surfaceContainerHigh, + child: InkWell( + onTap: () => PendingUploadsBanner._openSheet(context, albumId), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + children: [ + ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(4)), + child: SizedBox(width: 32, height: 32, child: Thumbnail.fromAsset(asset: previewAsset)), + ), + const SizedBox(width: 12), + Expanded( + child: Text( + '${'uploading'.t(context: context)} $count$percentLabel', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: context.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w500), + ), + ), + if (hasFailures) + Padding( + padding: const EdgeInsets.only(left: 8), + child: Icon(Icons.error_outline, color: context.colorScheme.error, size: 20), + ), + Icon(Icons.chevron_right_rounded, color: context.colorScheme.onSurfaceVariant), + ], + ), + ), + ), + SizedBox( + height: 3, + child: LinearProgressIndicator( + value: isIndeterminate ? null : overallProgress, + backgroundColor: context.colorScheme.surfaceContainerHighest, + valueColor: AlwaysStoppedAnimation( + hasFailures ? context.colorScheme.error : context.colorScheme.primary, + ), + ), + ), + ], + ), + ), + ); + } +} + +class _PendingUploadsSheet extends ConsumerWidget { + final String albumId; + + const _PendingUploadsSheet({required this.albumId}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final pending = ref.watch(pendingAlbumUploadsProvider(albumId)); + + // Auto-dismiss when the queue empties. + if (pending.isEmpty) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (Navigator.of(context).canPop()) { + Navigator.of(context).pop(); + } + }); + return const SizedBox.shrink(); + } + + final failedCount = pending.where((p) => p.failed).length; + + return SafeArea( + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 12), + child: Row( + children: [ + Expanded( + child: Text( + '${'uploading'.t(context: context)} (${pending.length})', + style: context.textTheme.titleMedium, + ), + ), + if (failedCount > 0) + TextButton.icon( + onPressed: () => ref.read(pendingAlbumUploadsProvider(albumId).notifier).clearFailed(), + icon: const Icon(Icons.clear_rounded, size: 18), + label: Text('clear_failed_count'.t(context: context, args: {'count': failedCount})), + style: TextButton.styleFrom(foregroundColor: context.colorScheme.error), + ), + ], + ), + ), + SizedBox( + height: 96, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: pending.length, + separatorBuilder: (_, __) => const SizedBox(width: 8), + itemBuilder: (_, index) => _PendingUploadTile(entry: pending[index]), + ), + ), + ], + ), + ), + ); + } +} + +class _PendingUploadTile extends StatelessWidget { + final PendingAlbumUpload entry; + + const _PendingUploadTile({required this.entry}); + + @override + Widget build(BuildContext context) { + return ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(8)), + child: SizedBox( + width: 96, + height: 96, + child: Stack( + fit: StackFit.expand, + children: [ + Thumbnail.fromAsset(asset: entry.asset), + Positioned.fill( + child: ColoredBox( + color: entry.failed ? Colors.red.withValues(alpha: 0.6) : Colors.black54, + child: Center( + child: entry.failed + ? const Icon(Icons.error_outline, color: Colors.white, size: 28) + : SizedBox( + width: 32, + height: 32, + child: CircularProgressIndicator( + value: entry.progress > 0 ? entry.progress : null, + strokeWidth: 2.5, + backgroundColor: Colors.white24, + valueColor: const AlwaysStoppedAnimation(Colors.white), + ), + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_page.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_page.widget.dart index e4676d6c34..b220049d94 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_page.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_page.widget.dart @@ -58,10 +58,13 @@ class _AssetPageState extends ConsumerState { _DragIntent _dragIntent = _DragIntent.none; Drag? _drag; + BaseAsset? _asset; + @override void initState() { super.initState(); _eventSubscription = EventStream.shared.listen(_onEvent); + _asset = ref.read(timelineServiceProvider).getAssetSafe(widget.index); WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted || !_scrollController.hasClients) { return; @@ -73,6 +76,14 @@ class _AssetPageState extends ConsumerState { }); } + @override + void didUpdateWidget(AssetPage oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.index != widget.index) { + _asset = ref.read(timelineServiceProvider).getAssetSafe(widget.index); + } + } + @override void dispose() { _scrollController.dispose(); @@ -387,7 +398,7 @@ class _AssetPageState extends ConsumerState { final isPlayingMotionVideo = ref.watch(isPlayingMotionVideoProvider); final timelineOrigin = ref.read(timelineServiceProvider).origin; - final asset = ref.read(timelineServiceProvider).getAssetSafe(widget.index); + final asset = _asset; if (asset == null) { return const Center(child: ImmichLoadingIndicator()); } diff --git a/mobile/lib/presentation/widgets/asset_viewer/bottom_bar.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/bottom_bar.widget.dart index 21401f37e5..e317c598f5 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/bottom_bar.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/bottom_bar.widget.dart @@ -7,6 +7,7 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/add_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/delete_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/edit_image_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/restore_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart'; @@ -55,9 +56,12 @@ class ViewerBottomBar extends ConsumerWidget { if (asset.hasRemote) AddActionButton(originalTheme: originalTheme), ], if (isOwner) ...[ - asset.isLocalOnly - ? const DeleteLocalActionButton(source: ActionSource.viewer) - : const DeleteActionButton(source: ActionSource.viewer, showConfirmation: true), + if (asset.isLocalOnly) + const DeleteLocalActionButton(source: ActionSource.viewer) + else if (asset.isTrashed) + const DeletePermanentActionButton(source: ActionSource.viewer, useShortLabel: true) + else + const DeleteActionButton(source: ActionSource.viewer, showConfirmation: true), ], ], ]; diff --git a/mobile/lib/presentation/widgets/asset_viewer/viewer_kebab_menu.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/viewer_kebab_menu.widget.dart index 308f6a72a3..5a79485daf 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/viewer_kebab_menu.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/viewer_kebab_menu.widget.dart @@ -50,7 +50,7 @@ class ViewerKebabMenu extends ConsumerWidget { timelineOrigin: timelineOrigin, ); - final menuChildren = ActionButtonBuilder.buildViewerKebabMenu(actionContext, context); + final menuChildren = ActionButtonBuilder.buildViewerKebabMenu(actionContext, context, ref); return MenuAnchor( consumeOutsideTap: true, diff --git a/mobile/lib/presentation/widgets/backup/backup_toggle_button.widget.dart b/mobile/lib/presentation/widgets/backup/backup_toggle_button.widget.dart index 7c92dc01d8..708d3a9879 100644 --- a/mobile/lib/presentation/widgets/backup/backup_toggle_button.widget.dart +++ b/mobile/lib/presentation/widgets/backup/backup_toggle_button.widget.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/metadata_key.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart'; class BackupToggleButton extends ConsumerStatefulWidget { final VoidCallback onStart; @@ -31,7 +31,7 @@ class BackupToggleButtonState extends ConsumerState with Sin end: 1, ).animate(CurvedAnimation(parent: _animationController, curve: Curves.easeInOut)); - _isEnabled = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup); + _isEnabled = ref.read(metadataProvider).appConfig.backup.enabled; } @override @@ -41,7 +41,7 @@ class BackupToggleButtonState extends ConsumerState with Sin } Future _onToggle(bool value) async { - await ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.enableBackup, value); + await ref.read(metadataProvider).write(MetadataKey.backupEnabled, value); setState(() { _isEnabled = value; diff --git a/mobile/lib/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart index 8753a9c14f..0bafacfe54 100644 --- a/mobile/lib/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart +++ b/mobile/lib/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart @@ -8,6 +8,7 @@ import 'package:immich_mobile/domain/models/setting.model.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/advanced_info_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/bulk_tag_assets_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/delete_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart'; @@ -26,6 +27,7 @@ import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.d import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/user_metadata.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; @@ -57,6 +59,9 @@ class _GeneralBottomSheetState extends ConsumerState { final multiselect = ref.watch(multiSelectProvider); final isTrashEnable = ref.watch(serverInfoProvider.select((state) => state.serverFeatures.trash)); final advancedTroubleshooting = ref.watch(settingsProvider.notifier).get(Setting.advancedTroubleshooting); + final tagsEnabled = ref.watch( + userMetadataPreferencesProvider.select((value) => value.valueOrNull?.tagsEnabled ?? false), + ); Future addAssetsToAlbum(RemoteAlbum album) async { final selectedAssets = multiselect.selectedAssets; @@ -114,6 +119,7 @@ class _GeneralBottomSheetState extends ConsumerState { : const DeletePermanentActionButton(source: ActionSource.timeline), const FavoriteActionButton(source: ActionSource.timeline), const ArchiveActionButton(source: ActionSource.timeline), + if (tagsEnabled) const BulkTagAssetsActionButton(source: ActionSource.timeline), const EditDateTimeActionButton(source: ActionSource.timeline), const EditLocationActionButton(source: ActionSource.timeline), const MoveToLockFolderActionButton(source: ActionSource.timeline), diff --git a/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart b/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart index 8720cc4253..286e874e1b 100644 --- a/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart +++ b/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart @@ -120,6 +120,9 @@ class _ThumbnailTileState extends ConsumerState { }, flightShuttleBuilder: (context, animation, direction, from, to) { void animationStatusListener(AnimationStatus status) { + if (!mounted) { + return; + } final heroInFlight = status == AnimationStatus.forward || status == AnimationStatus.reverse; if (_hideIndicators != heroInFlight) { setState(() => _hideIndicators = heroInFlight); diff --git a/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart b/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart index 250cea8229..ea6f3e739e 100644 --- a/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart +++ b/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart @@ -242,7 +242,11 @@ class _AssetTileWidget extends ConsumerWidget { return false; } - return lockSelectionAssets.contains(asset); + // Iterate with `==` instead of `Set.contains` because `RemoteAsset.hashCode` + // includes `localId` while `==` does not — so the same server asset can + // hash to a different bucket when its `localId` differs (e.g., album-fetched + // copy has localId=null, merged-timeline copy has it populated). + return lockSelectionAssets.any((a) => a == asset); } @override diff --git a/mobile/lib/presentation/widgets/timeline/timeline.widget.dart b/mobile/lib/presentation/widgets/timeline/timeline.widget.dart index 8974b20d1a..2660b0da31 100644 --- a/mobile/lib/presentation/widgets/timeline/timeline.widget.dart +++ b/mobile/lib/presentation/widgets/timeline/timeline.widget.dart @@ -64,36 +64,32 @@ class Timeline extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, - floatingActionButton: const DownloadStatusFloatingButton(), - body: LayoutBuilder( - builder: (_, constraints) => ProviderScope( - overrides: [ - timelineArgsProvider.overrideWith( - (ref) => TimelineArgs( - maxWidth: constraints.maxWidth, - maxHeight: constraints.maxHeight, - columnCount: ref.watch(appConfigProvider.select((config) => config.timeline.tilesPerRow)), - showStorageIndicator: showStorageIndicator, - withStack: withStack, - groupBy: groupBy, - ), + return LayoutBuilder( + builder: (_, constraints) => ProviderScope( + overrides: [ + timelineArgsProvider.overrideWith( + (ref) => TimelineArgs( + maxWidth: constraints.maxWidth, + maxHeight: constraints.maxHeight, + columnCount: ref.watch(appConfigProvider.select((config) => config.timeline.tilesPerRow)), + showStorageIndicator: showStorageIndicator, + withStack: withStack, + groupBy: groupBy, ), - if (readOnly) readonlyModeProvider.overrideWith(() => _AlwaysReadOnlyNotifier()), - ], - child: _SliverTimeline( - topSliverWidget: topSliverWidget, - topSliverWidgetHeight: topSliverWidgetHeight, - bottomSliverWidget: bottomSliverWidget, - appBar: appBar, - bottomSheet: bottomSheet, - withScrubber: withScrubber, - persistentBottomBar: persistentBottomBar, - snapToMonth: snapToMonth, - maxWidth: constraints.maxWidth, - loadingWidget: loadingWidget, ), + if (readOnly) readonlyModeProvider.overrideWith(() => _AlwaysReadOnlyNotifier()), + ], + child: _SliverTimeline( + topSliverWidget: topSliverWidget, + topSliverWidgetHeight: topSliverWidgetHeight, + bottomSliverWidget: bottomSliverWidget, + appBar: appBar, + bottomSheet: bottomSheet, + withScrubber: withScrubber, + persistentBottomBar: persistentBottomBar, + snapToMonth: snapToMonth, + maxWidth: constraints.maxWidth, + loadingWidget: loadingWidget, ), ), ); @@ -379,121 +375,126 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> { ref.read(multiSelectProvider.notifier).reset(); } }, - child: asyncSegments.widgetWhen( - onLoading: widget.loadingWidget != null ? () => widget.loadingWidget! : null, - onData: (segments) { - final childCount = (segments.lastOrNull?.lastIndex ?? -1) + 1; - final double appBarExpandedHeight = widget.appBar != null && widget.appBar is MesmerizingSliverAppBar - ? 200 - : 0; - final topPadding = context.padding.top + (widget.appBar == null ? 0 : kToolbarHeight) + 10; + child: PrimaryScrollController( + controller: _scrollController, + child: Scaffold( + resizeToAvoidBottomInset: false, + floatingActionButton: const DownloadStatusFloatingButton(), + body: asyncSegments.widgetWhen( + onLoading: widget.loadingWidget != null ? () => widget.loadingWidget! : null, + onData: (segments) { + final childCount = (segments.lastOrNull?.lastIndex ?? -1) + 1; + final double appBarExpandedHeight = widget.appBar != null && widget.appBar is MesmerizingSliverAppBar + ? 200 + : 0; + final topPadding = context.padding.top + (widget.appBar == null ? 0 : kToolbarHeight) + 10; - const bottomSheetOpenModifier = 120.0; - final contentBottomPadding = context.padding.bottom + (isMultiSelectEnabled ? bottomSheetOpenModifier : 0); - final scrubberBottomPadding = contentBottomPadding + kScrubberThumbHeight; + const bottomSheetOpenModifier = 120.0; + final contentBottomPadding = + context.padding.bottom + (isMultiSelectEnabled ? bottomSheetOpenModifier : 0); + final scrubberBottomPadding = contentBottomPadding + kScrubberThumbHeight; - final grid = CustomScrollView( - primary: true, - physics: _scrollPhysics, - cacheExtent: maxHeight * 2, - slivers: [ - if (isSelectionMode) const SelectionSliverAppBar() else if (widget.appBar != null) widget.appBar!, - if (widget.topSliverWidget != null) widget.topSliverWidget!, - _SliverSegmentedList( - segments: segments, - delegate: SliverChildBuilderDelegate( - (ctx, index) { - if (index >= childCount) { - return null; - } - final segment = segments.findByIndex(index); - return segment?.builder(ctx, index) ?? const SizedBox.shrink(); - }, - childCount: childCount, - addAutomaticKeepAlives: false, - // We add repaint boundary around tiles, so skip the auto boundaries - addRepaintBoundaries: false, - ), - ), - if (widget.bottomSliverWidget != null) widget.bottomSliverWidget!, - SliverPadding(padding: EdgeInsets.only(bottom: contentBottomPadding)), - ], - ); + final grid = CustomScrollView( + primary: true, + physics: _scrollPhysics, + cacheExtent: maxHeight * 2, + slivers: [ + if (isSelectionMode) const SelectionSliverAppBar() else if (widget.appBar != null) widget.appBar!, + if (widget.topSliverWidget != null) widget.topSliverWidget!, + _SliverSegmentedList( + segments: segments, + delegate: SliverChildBuilderDelegate( + (ctx, index) { + if (index >= childCount) { + return null; + } + final segment = segments.findByIndex(index); + return segment?.builder(ctx, index) ?? const SizedBox.shrink(); + }, + childCount: childCount, + addAutomaticKeepAlives: false, + // We add repaint boundary around tiles, so skip the auto boundaries + addRepaintBoundaries: false, + ), + ), + if (widget.bottomSliverWidget != null) widget.bottomSliverWidget!, + SliverPadding(padding: EdgeInsets.only(bottom: contentBottomPadding)), + ], + ); - final Widget timeline; - if (widget.withScrubber) { - timeline = Scrubber( - snapToMonth: widget.snapToMonth, - layoutSegments: segments, - timelineHeight: maxHeight, - topPadding: topPadding, - bottomPadding: scrubberBottomPadding, - monthSegmentSnappingOffset: widget.topSliverWidgetHeight ?? 0 + appBarExpandedHeight, - hasAppBar: widget.appBar != null, - child: grid, - ); - } else { - timeline = grid; - } + final Widget timeline; + if (widget.withScrubber) { + timeline = Scrubber( + snapToMonth: widget.snapToMonth, + layoutSegments: segments, + timelineHeight: maxHeight, + topPadding: topPadding, + bottomPadding: scrubberBottomPadding, + monthSegmentSnappingOffset: widget.topSliverWidgetHeight ?? 0 + appBarExpandedHeight, + hasAppBar: widget.appBar != null, + child: grid, + ); + } else { + timeline = grid; + } - return PrimaryScrollController( - controller: _scrollController, - child: RawGestureDetector( - gestures: { - CustomScaleGestureRecognizer: GestureRecognizerFactoryWithHandlers( - () => CustomScaleGestureRecognizer(), - (CustomScaleGestureRecognizer scale) { - scale.onStart = (details) { - _baseScaleFactor = _scaleFactor; - }; + return RawGestureDetector( + gestures: { + CustomScaleGestureRecognizer: GestureRecognizerFactoryWithHandlers( + () => CustomScaleGestureRecognizer(), + (CustomScaleGestureRecognizer scale) { + scale.onStart = (details) { + _baseScaleFactor = _scaleFactor; + }; - scale.onUpdate = (details) { - final newScaleFactor = math.max(math.min(5.0, _baseScaleFactor * details.scale), 1.0); - final newPerRow = 7 - newScaleFactor.toInt(); + scale.onUpdate = (details) { + final newScaleFactor = math.max(math.min(5.0, _baseScaleFactor * details.scale), 1.0); + final newPerRow = 7 - newScaleFactor.toInt(); - if (newPerRow != _perRow) { - final targetAssetIndex = _getCurrentAssetIndex(segments); - setState(() { - _scaleFactor = newScaleFactor; - _perRow = newPerRow; - _restoreAssetIndex = targetAssetIndex; - }); + if (newPerRow != _perRow) { + final targetAssetIndex = _getCurrentAssetIndex(segments); + setState(() { + _scaleFactor = newScaleFactor; + _perRow = newPerRow; + _restoreAssetIndex = targetAssetIndex; + }); - ref.read(metadataProvider).write(MetadataKey.timelineTilesPerRow, _perRow); - } - }; - }, - ), - }, - child: TimelineDragRegion( - onStart: !isReadonlyModeEnabled ? _setDragStartIndex : null, - onAssetEnter: _handleDragAssetEnter, - onEnd: !isReadonlyModeEnabled ? _stopDrag : null, - onScroll: _dragScroll, - onScrollStart: () { - // Minimize the bottom sheet when drag selection starts - ref.read(timelineStateProvider.notifier).setScrolling(true); + ref.read(metadataProvider).write(MetadataKey.timelineTilesPerRow, _perRow); + } + }; + }, + ), }, - child: Stack( - clipBehavior: Clip.none, - children: [ - timeline, - if (isBottomWidgetVisible) - Positioned( - top: MediaQuery.paddingOf(context).top, - left: 25, - child: const SizedBox( - height: kToolbarHeight, - child: Center(child: _MultiSelectStatusButton()), + child: TimelineDragRegion( + onStart: !isReadonlyModeEnabled ? _setDragStartIndex : null, + onAssetEnter: _handleDragAssetEnter, + onEnd: !isReadonlyModeEnabled ? _stopDrag : null, + onScroll: _dragScroll, + onScrollStart: () { + // Minimize the bottom sheet when drag selection starts + ref.read(timelineStateProvider.notifier).setScrolling(true); + }, + child: Stack( + clipBehavior: Clip.none, + children: [ + timeline, + if (isBottomWidgetVisible) + Positioned( + top: MediaQuery.paddingOf(context).top, + left: 25, + child: const SizedBox( + height: kToolbarHeight, + child: Center(child: _MultiSelectStatusButton()), + ), ), - ), - if (isBottomWidgetVisible) widget.bottomSheet!, - ], + if (isBottomWidgetVisible) widget.bottomSheet!, + ], + ), ), - ), - ), - ); - }, + ); + }, + ), + ), ), ); } diff --git a/mobile/lib/providers/album/pending_album_uploads.provider.dart b/mobile/lib/providers/album/pending_album_uploads.provider.dart new file mode 100644 index 0000000000..db857ba3c0 --- /dev/null +++ b/mobile/lib/providers/album/pending_album_uploads.provider.dart @@ -0,0 +1,81 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; + +class PendingAlbumUpload { + final LocalAsset asset; + final double progress; + final bool failed; + + const PendingAlbumUpload({required this.asset, this.progress = 0.0, this.failed = false}); + + PendingAlbumUpload copyWith({double? progress, bool? failed}) => + PendingAlbumUpload(asset: asset, progress: progress ?? this.progress, failed: failed ?? this.failed); +} + +class AlbumPendingUploadsNotifier extends AutoDisposeFamilyNotifier, String> { + KeepAliveLink? _keepAliveLink; + + @override + List build(String albumId) { + ref.onDispose(() { + _keepAliveLink?.close(); + _keepAliveLink = null; + }); + + return const []; + } + + void enqueue(Iterable assets) { + if (assets.isEmpty) { + return; + } + + final existingIds = state.map((e) => e.asset.id).toSet(); + final additions = assets.where((a) => !existingIds.contains(a.id)).map((a) => PendingAlbumUpload(asset: a)); + state = [...state, ...additions]; + _syncKeepAlive(); + } + + void updateProgress(String localAssetId, double progress) { + state = [ + for (final entry in state) + if (entry.asset.id == localAssetId) entry.copyWith(progress: progress, failed: false) else entry, + ]; + _syncKeepAlive(); + } + + void markFailed(String localAssetId) { + state = [ + for (final entry in state) + if (entry.asset.id == localAssetId) entry.copyWith(failed: true) else entry, + ]; + _syncKeepAlive(); + } + + void markAllFailed() { + state = [for (final entry in state) entry.copyWith(failed: true)]; + _syncKeepAlive(); + } + + void remove(String localAssetId) { + state = state.where((e) => e.asset.id != localAssetId).toList(); + _syncKeepAlive(); + } + + void clearFailed() { + state = state.where((e) => !e.failed).toList(); + _syncKeepAlive(); + } + + void _syncKeepAlive() { + if (state.isEmpty) { + _keepAliveLink?.close(); + _keepAliveLink = null; + } else { + _keepAliveLink ??= ref.keepAlive(); + } + } +} + +final pendingAlbumUploadsProvider = NotifierProvider.autoDispose + .family, String>(AlbumPendingUploadsNotifier.new); diff --git a/mobile/lib/providers/app_life_cycle.provider.dart b/mobile/lib/providers/app_life_cycle.provider.dart index 11e0dcd49c..3b40ca2f63 100644 --- a/mobile/lib/providers/app_life_cycle.provider.dart +++ b/mobile/lib/providers/app_life_cycle.provider.dart @@ -5,16 +5,15 @@ import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/services/log.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; import 'package:immich_mobile/providers/gallery_permission.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; import 'package:immich_mobile/providers/notification_permission.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:logging/logging.dart'; enum AppLifeCycleEnum { active, inactive, paused, resumed, detached, hidden } @@ -108,7 +107,7 @@ class AppLifeCycleNotifier extends StateNotifier { await Future.delayed(const Duration(milliseconds: 500)); final backgroundManager = _ref.read(backgroundSyncProvider); - final isAlbumLinkedSyncEnable = _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.syncAlbums); + final isAlbumLinkedSyncEnable = _ref.read(metadataProvider).appConfig.backup.syncAlbums; try { bool syncSuccess = false; @@ -138,7 +137,7 @@ class AppLifeCycleNotifier extends StateNotifier { } Future _resumeBackup() async { - final isEnableBackup = _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup); + final isEnableBackup = _ref.read(metadataProvider).appConfig.backup.enabled; if (isEnableBackup) { final currentUser = Store.tryGet(StoreKey.currentUser); diff --git a/mobile/lib/providers/auth.provider.dart b/mobile/lib/providers/auth.provider.dart index 4c2a110fde..ae97909349 100644 --- a/mobile/lib/providers/auth.provider.dart +++ b/mobile/lib/providers/auth.provider.dart @@ -1,6 +1,9 @@ +import 'dart:convert'; + import 'package:flutter_udid/flutter_udid.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/constants.dart'; +import 'package:immich_mobile/domain/models/metadata_key.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/services/user.service.dart'; @@ -8,6 +11,7 @@ import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/models/auth/auth_state.model.dart'; import 'package:immich_mobile/models/auth/login_response.model.dart'; import 'package:immich_mobile/providers/api.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart'; import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/auth.service.dart'; @@ -126,7 +130,8 @@ class AuthNotifier extends StateNotifier { await _apiService.updateHeaders(); final serverEndpoint = Store.get(StoreKey.serverEndpoint); - final customHeaders = Store.tryGet(StoreKey.customHeaders); + final headerMap = _ref.read(metadataProvider).systemConfig.network.customHeaders; + final customHeaders = headerMap.isEmpty ? null : jsonEncode(headerMap); await _widgetService.writeCredentials(serverEndpoint, accessToken, customHeaders); // Get the deviceid from the store if it exists, otherwise generate a new one @@ -174,19 +179,19 @@ class AuthNotifier extends StateNotifier { } Future saveWifiName(String wifiName) async { - await Store.put(StoreKey.preferredWifiName, wifiName); + await _ref.read(metadataProvider).write(MetadataKey.networkPreferredWifiName, wifiName); } Future saveLocalEndpoint(String url) async { - await Store.put(StoreKey.localEndpoint, url); + await _ref.read(metadataProvider).write(MetadataKey.networkLocalEndpoint, url); } String? getSavedWifiName() { - return Store.tryGet(StoreKey.preferredWifiName); + return _ref.read(metadataProvider).systemConfig.network.preferredWifiName; } String? getSavedLocalEndpoint() { - return Store.tryGet(StoreKey.localEndpoint); + return _ref.read(metadataProvider).systemConfig.network.localEndpoint; } /// Returns the current server endpoint (with /api) URL from the store diff --git a/mobile/lib/providers/infrastructure/action.provider.dart b/mobile/lib/providers/infrastructure/action.provider.dart index 46a4615ca3..bad7f67199 100644 --- a/mobile/lib/providers/infrastructure/action.provider.dart +++ b/mobile/lib/providers/infrastructure/action.provider.dart @@ -13,6 +13,7 @@ import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart' import 'package:immich_mobile/providers/backup/asset_upload_progress.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset_viewer/asset.provider.dart' show assetExifProvider; +import 'package:immich_mobile/providers/infrastructure/tag.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart'; @@ -354,6 +355,23 @@ class ActionNotifier extends Notifier { } } + Future tagAssets(ActionSource source, BuildContext context) async { + final ids = _getOwnedRemoteIdsForSource(source); + try { + final count = await _service.tagAssets(ids, context); + if (count == null) { + return null; + } + + ref.invalidate(tagProvider); + return ActionResult(count: count, success: true); + } catch (error, stack) { + _logger.severe('Failed to tag assets', error, stack); + ref.invalidate(tagProvider); + return ActionResult(count: ids.length, success: false, error: error.toString()); + } + } + Future removeFromAlbum(ActionSource source, String albumId) async { final ids = _getRemoteIdsForSource(source); try { diff --git a/mobile/lib/providers/infrastructure/album.provider.dart b/mobile/lib/providers/infrastructure/album.provider.dart index 1ddabc1604..379e7b3101 100644 --- a/mobile/lib/providers/infrastructure/album.provider.dart +++ b/mobile/lib/providers/infrastructure/album.provider.dart @@ -9,6 +9,7 @@ import 'package:immich_mobile/infrastructure/repositories/remote_album.repositor import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/remote_album.provider.dart'; import 'package:immich_mobile/repositories/drift_album_api_repository.dart'; +import 'package:immich_mobile/services/foreground_upload.service.dart'; final localAlbumRepository = Provider( (ref) => DriftLocalAlbumRepository(ref.watch(driftProvider)), @@ -33,7 +34,11 @@ final remoteAlbumRepository = Provider( ); final remoteAlbumServiceProvider = Provider( - (ref) => RemoteAlbumService(ref.watch(remoteAlbumRepository), ref.watch(driftAlbumApiRepositoryProvider)), + (ref) => RemoteAlbumService( + ref.watch(remoteAlbumRepository), + ref.watch(driftAlbumApiRepositoryProvider), + ref.watch(foregroundUploadServiceProvider), + ), dependencies: [remoteAlbumRepository], ); diff --git a/mobile/lib/providers/infrastructure/platform.provider.dart b/mobile/lib/providers/infrastructure/platform.provider.dart index 01d0f61d1c..9f85235927 100644 --- a/mobile/lib/providers/infrastructure/platform.provider.dart +++ b/mobile/lib/providers/infrastructure/platform.provider.dart @@ -3,9 +3,10 @@ import 'package:immich_mobile/domain/services/background_worker.service.dart'; import 'package:immich_mobile/platform/background_worker_api.g.dart'; import 'package:immich_mobile/platform/background_worker_lock_api.g.dart'; import 'package:immich_mobile/platform/connectivity_api.g.dart'; -import 'package:immich_mobile/platform/native_sync_api.g.dart'; import 'package:immich_mobile/platform/local_image_api.g.dart'; +import 'package:immich_mobile/platform/native_sync_api.g.dart'; import 'package:immich_mobile/platform/network_api.g.dart'; +import 'package:immich_mobile/platform/permission_api.g.dart'; import 'package:immich_mobile/platform/remote_image_api.g.dart'; final backgroundWorkerFgServiceProvider = Provider((_) => BackgroundWorkerFgService(BackgroundWorkerFgHostApi())); @@ -16,6 +17,8 @@ final backgroundWorkerLockServiceProvider = Provider((_) => NativeSyncApi()); +final permissionApiProvider = Provider((_) => PermissionApi()); + final connectivityApiProvider = Provider((_) => ConnectivityApi()); final localImageApi = LocalImageApi(); diff --git a/mobile/lib/providers/infrastructure/remote_album.provider.dart b/mobile/lib/providers/infrastructure/remote_album.provider.dart index b9a0e91ce5..73a796bd31 100644 --- a/mobile/lib/providers/infrastructure/remote_album.provider.dart +++ b/mobile/lib/providers/infrastructure/remote_album.provider.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:collection/collection.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; @@ -6,8 +8,10 @@ import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/services/remote_album.service.dart'; import 'package:immich_mobile/models/albums/album_search.model.dart'; import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; +import 'package:immich_mobile/providers/album/pending_album_uploads.provider.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/services/foreground_upload.service.dart'; import 'package:logging/logging.dart'; class RemoteAlbumState { @@ -105,6 +109,46 @@ class RemoteAlbumNotifier extends Notifier { } } + /// Creates an album from a heterogeneous asset selection. Already-remote + /// assets seed the album immediately; local-only assets are uploaded in the + /// background and linked one-by-one as each upload completes. + Future createAlbumWithAssets({ + required String title, + String? description, + Iterable assets = const [], + }) async { + try { + final currentUser = ref.read(currentUserProvider); + if (currentUser == null) { + throw Exception('User not logged in'); + } + + final candidates = RemoteAlbumService.categorizeCandidates(assets); + final album = await _remoteAlbumService.createAlbum( + title: title, + owner: currentUser, + description: description, + assetIds: candidates.remoteAssetIds, + ); + + state = state.copyWith(albums: [...state.albums, album]); + + if (candidates.localAssetsToUpload.isNotEmpty) { + unawaited( + addAssetsToAlbum( + album.id, + candidates.localAssetsToUpload, + ).then((_) {}).catchError((Object _, StackTrace _) {}), + ); + } + + return album; + } catch (error, stack) { + _logger.severe('Failed to create album with assets', error, stack); + rethrow; + } + } + Future updateAlbum( String albumId, { String? name, @@ -155,8 +199,65 @@ class RemoteAlbumNotifier extends Notifier { return _remoteAlbumService.getAssets(albumId); } - Future addAssets(String albumId, List assetIds) { - return _remoteAlbumService.addAssets(albumId: albumId, assetIds: assetIds); + Future addAssets(String albumId, List assetIds) async { + final added = await _remoteAlbumService.addAssets(albumId: albumId, assetIds: assetIds); + if (added > 0) { + await _refreshAlbumInState(albumId); + } + return added; + } + + /// Adds a heterogeneous asset selection to an album. Already-remote assets + /// are linked immediately; local-only assets are queued in + /// [pendingAlbumUploadsProvider] (so the album page can show them with + /// progress indicators), uploaded, and linked one-by-one as each finishes. + Future addAssetsToAlbum(String albumId, Iterable assets) async { + final currentUser = ref.read(currentUserProvider); + if (currentUser == null) { + throw Exception('User not logged in'); + } + + final candidates = RemoteAlbumService.categorizeCandidates(assets); + final pendingNotifier = ref.read(pendingAlbumUploadsProvider(albumId).notifier); + pendingNotifier.enqueue(candidates.localAssetsToUpload); + + try { + final added = await _remoteAlbumService.addAssetsToAlbum( + albumId: albumId, + uploader: currentUser, + candidates: candidates, + uploadCallbacks: UploadCallbacks( + onProgress: (localAssetId, _, bytes, totalBytes) { + final progress = totalBytes > 0 ? bytes / totalBytes : 0.0; + pendingNotifier.updateProgress(localAssetId, progress); + }, + onSuccess: (localAssetId, _) => pendingNotifier.remove(localAssetId), + onError: (localAssetId, _) => pendingNotifier.markFailed(localAssetId), + ), + ); + if (added > 0) { + await _refreshAlbumInState(albumId); + } + return added; + } catch (error, stack) { + if (candidates.localAssetsToUpload.isNotEmpty) { + pendingNotifier.markAllFailed(); + } + _logger.severe('Failed to add assets to album $albumId', error, stack); + rethrow; + } + } + + /// Re-reads a single album from the local DB and replaces it in [state] so + /// that views bound to the album list (counts, thumbnails) reflect the + /// latest junction-table changes without a full `refresh()`. + Future _refreshAlbumInState(String albumId) async { + final updated = await _remoteAlbumService.get(albumId); + if (updated == null) { + return; + } + + state = state.copyWith(albums: state.albums.map((album) => album.id == albumId ? updated : album).toList()); } Future addUsers(String albumId, List userIds) { diff --git a/mobile/lib/providers/infrastructure/sync.provider.dart b/mobile/lib/providers/infrastructure/sync.provider.dart index 5b9f29225e..75c8e09326 100644 --- a/mobile/lib/providers/infrastructure/sync.provider.dart +++ b/mobile/lib/providers/infrastructure/sync.provider.dart @@ -11,8 +11,8 @@ import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/cancel.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/storage.provider.dart'; -import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; +import 'package:immich_mobile/repositories/asset_media.repository.dart'; +import 'package:immich_mobile/repositories/permission.repository.dart'; final syncMigrationRepositoryProvider = Provider((ref) => SyncMigrationRepository(ref.watch(driftProvider))); @@ -22,8 +22,8 @@ final syncStreamServiceProvider = Provider( syncStreamRepository: ref.watch(syncStreamRepositoryProvider), localAssetRepository: ref.watch(localAssetRepository), trashedLocalAssetRepository: ref.watch(trashedLocalAssetRepository), - localFilesManager: ref.watch(localFilesManagerRepositoryProvider), - storageRepository: ref.watch(storageRepositoryProvider), + assetMediaRepository: ref.watch(assetMediaRepositoryProvider), + permissionRepository: ref.watch(permissionRepositoryProvider), syncMigrationRepository: ref.watch(syncMigrationRepositoryProvider), api: ref.watch(apiServiceProvider), cancelChecker: ref.watch(cancellationProvider), @@ -39,8 +39,8 @@ final localSyncServiceProvider = Provider( localAlbumRepository: ref.watch(localAlbumRepository), localAssetRepository: ref.watch(localAssetRepository), trashedLocalAssetRepository: ref.watch(trashedLocalAssetRepository), - localFilesManager: ref.watch(localFilesManagerRepositoryProvider), - storageRepository: ref.watch(storageRepositoryProvider), + assetMediaRepository: ref.watch(assetMediaRepositoryProvider), + permissionRepository: ref.watch(permissionRepositoryProvider), nativeSyncApi: ref.watch(nativeSyncApiProvider), ), ); diff --git a/mobile/lib/providers/infrastructure/tag.provider.dart b/mobile/lib/providers/infrastructure/tag.provider.dart index 23d4d86861..2d527768c5 100644 --- a/mobile/lib/providers/infrastructure/tag.provider.dart +++ b/mobile/lib/providers/infrastructure/tag.provider.dart @@ -1,16 +1,22 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/tag.model.dart'; -import 'package:immich_mobile/infrastructure/repositories/tags_api.repository.dart'; +import 'package:immich_mobile/domain/services/tag.service.dart'; class TagNotifier extends AsyncNotifier> { @override Future> build() async { - final repo = ref.read(tagsApiRepositoryProvider); - final allTags = await repo.getAllTags(); - if (allTags == null) { - return {}; - } - return allTags.map((t) => Tag.fromDto(t)).toSet(); + return ref.watch(tagServiceProvider).getAllTags(); + } + + Future bulkTagAssets(List assetIds, List tagIds) async { + return ref.read(tagServiceProvider).bulkTagAssets(assetIds, tagIds); + } + + Future> upsertTags(List tags) async { + final upsertedTags = await ref.read(tagServiceProvider).upsertTags(tags); + + state = AsyncValue.data({...?state.valueOrNull, ...upsertedTags}); + return upsertedTags; } } diff --git a/mobile/lib/providers/websocket.provider.dart b/mobile/lib/providers/websocket.provider.dart index 5450120316..a53f0aaaeb 100644 --- a/mobile/lib/providers/websocket.provider.dart +++ b/mobile/lib/providers/websocket.provider.dart @@ -7,6 +7,7 @@ import 'package:immich_mobile/infrastructure/repositories/network.repository.dar import 'package:immich_mobile/models/server_info/server_version.model.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/utils/debounce.dart'; import 'package:immich_mobile/utils/debug_print.dart'; @@ -192,7 +193,7 @@ class WebsocketNotifier extends StateNotifier { return; } - final isSyncAlbumEnabled = Store.get(StoreKey.syncAlbums, false); + final isSyncAlbumEnabled = _ref.read(metadataProvider).appConfig.backup.syncAlbums; try { unawaited( _ref.read(backgroundSyncProvider).syncWebsocketBatchV1(_batchedAssetUploadReady.toList()).then((_) { @@ -213,7 +214,7 @@ class WebsocketNotifier extends StateNotifier { return; } - final isSyncAlbumEnabled = Store.get(StoreKey.syncAlbums, false); + final isSyncAlbumEnabled = _ref.read(metadataProvider).appConfig.backup.syncAlbums; try { unawaited( _ref.read(backgroundSyncProvider).syncWebsocketBatchV2(_batchedAssetUploadReady.toList()).then((_) { diff --git a/mobile/lib/repositories/asset_media.repository.dart b/mobile/lib/repositories/asset_media.repository.dart index a2d8bfe162..6b34d1855f 100644 --- a/mobile/lib/repositories/asset_media.repository.dart +++ b/mobile/lib/repositories/asset_media.repository.dart @@ -8,19 +8,24 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/extensions/response_extensions.dart'; +import 'package:immich_mobile/platform/native_sync_api.g.dart'; +import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; import 'package:immich_mobile/repositories/asset_api.repository.dart'; import 'package:logging/logging.dart'; import 'package:path_provider/path_provider.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:share_plus/share_plus.dart'; -final assetMediaRepositoryProvider = Provider((ref) => AssetMediaRepository(ref.watch(assetApiRepositoryProvider))); +final assetMediaRepositoryProvider = Provider( + (ref) => AssetMediaRepository(ref.watch(assetApiRepositoryProvider), ref.watch(nativeSyncApiProvider)), +); class AssetMediaRepository { final AssetApiRepository _assetApiRepository; + final NativeSyncApi _nativeSyncApi; static final Logger _log = Logger("AssetMediaRepository"); - const AssetMediaRepository(this._assetApiRepository); + const AssetMediaRepository(this._assetApiRepository, this._nativeSyncApi); Future _androidSupportsTrash() async { if (Platform.isAndroid) { @@ -45,6 +50,27 @@ class AssetMediaRepository { return PhotoManager.editor.deleteWithIds(ids); } + Future _restoreFromTrashById(String mediaId, int type) async { + try { + return await _nativeSyncApi.restoreFromTrashById(mediaId, type); + } catch (e, s) { + _log.warning('Error restore file from trash by Id', e, s); + return false; + } + } + + Future> restoreAssetsFromTrash(Iterable assets) async { + final restoredIds = []; + for (final asset in assets) { + _log.info("Restoring from trash, localId: ${asset.id}, checksum: ${asset.checksum}"); + final result = await _restoreFromTrashById(asset.id, asset.type.index); + if (result) { + restoredIds.add(asset.id); + } + } + return restoredIds; + } + Future get(String id) async { final entity = await AssetEntity.fromId(id); return entity; diff --git a/mobile/lib/repositories/auth.repository.dart b/mobile/lib/repositories/auth.repository.dart index c16b728ae5..e71c752f44 100644 --- a/mobile/lib/repositories/auth.repository.dart +++ b/mobile/lib/repositories/auth.repository.dart @@ -1,46 +1,40 @@ -import 'dart:convert'; - import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart'; import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart'; -final authRepositoryProvider = Provider((ref) => AuthRepository(ref.watch(driftProvider))); +final authRepositoryProvider = Provider( + (ref) => AuthRepository(ref.watch(driftProvider), ref.watch(metadataProvider)), +); class AuthRepository { final Drift _drift; + final MetadataRepository _metadata; - const AuthRepository(this._drift); + const AuthRepository(this._drift, this._metadata); Future clearLocalData() async { await SyncStreamRepository(_drift).reset(); } bool getEndpointSwitchingFeature() { - return Store.tryGet(StoreKey.autoEndpointSwitching) ?? false; + return _metadata.systemConfig.network.autoEndpointSwitching; } String? getPreferredWifiName() { - return Store.tryGet(StoreKey.preferredWifiName); + return _metadata.systemConfig.network.preferredWifiName; } String? getLocalEndpoint() { - return Store.tryGet(StoreKey.localEndpoint); + return _metadata.systemConfig.network.localEndpoint; } List getExternalEndpointList() { - final jsonString = Store.tryGet(StoreKey.externalEndpointList); - - if (jsonString == null) { - return []; - } - - final List jsonList = jsonDecode(jsonString); - final endpointList = jsonList.map((e) => AuxilaryEndpoint.fromJson(e)).toList(); - - return endpointList; + return _metadata.systemConfig.network.externalEndpointList + .map((url) => AuxilaryEndpoint(url: url, status: .valid)) + .toList(); } } diff --git a/mobile/lib/repositories/local_files_manager.repository.dart b/mobile/lib/repositories/local_files_manager.repository.dart deleted file mode 100644 index 6a6200b2e1..0000000000 --- a/mobile/lib/repositories/local_files_manager.repository.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/services/local_files_manager.service.dart'; -import 'package:logging/logging.dart'; - -final localFilesManagerRepositoryProvider = Provider( - (ref) => LocalFilesManagerRepository(ref.watch(localFileManagerServiceProvider)), -); - -class LocalFilesManagerRepository { - LocalFilesManagerRepository(this._service); - - final Logger _logger = Logger('LocalFilesManagerRepo'); - final LocalFilesManagerService _service; - - Future moveToTrash(List mediaUrls) async { - return await _service.moveToTrash(mediaUrls); - } - - Future restoreFromTrash(String fileName, int type) async { - return await _service.restoreFromTrash(fileName, type); - } - - Future requestManageMediaPermission() async { - return await _service.requestManageMediaPermission(); - } - - Future hasManageMediaPermission() async { - return await _service.hasManageMediaPermission(); - } - - Future manageMediaPermission() async { - return await _service.manageMediaPermission(); - } - - Future> restoreAssetsFromTrash(Iterable assets) async { - final restoredIds = []; - for (final asset in assets) { - _logger.info("Restoring from trash, localId: ${asset.id}, remoteId: ${asset.checksum}"); - try { - final result = await _service.restoreFromTrashById(asset.id, asset.type.index); - if (result) { - restoredIds.add(asset.id); - } - } catch (e) { - _logger.warning("Restoring failure: $e"); - } - } - return restoredIds; - } -} diff --git a/mobile/lib/repositories/permission.repository.dart b/mobile/lib/repositories/permission.repository.dart index 74230a3652..e4b6d99f25 100644 --- a/mobile/lib/repositories/permission.repository.dart +++ b/mobile/lib/repositories/permission.repository.dart @@ -1,12 +1,16 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/platform/permission_api.g.dart'; +import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; import 'package:permission_handler/permission_handler.dart'; -final permissionRepositoryProvider = Provider((_) { - return const PermissionRepository(); +final permissionRepositoryProvider = Provider((ref) { + return PermissionRepository(ref.watch(permissionApiProvider)); }); class PermissionRepository implements IPermissionRepository { - const PermissionRepository(); + final PermissionApi _permissionApi; + + const PermissionRepository(this._permissionApi); @override Future hasLocationWhenInUsePermission() { @@ -34,6 +38,21 @@ class PermissionRepository implements IPermissionRepository { Future openSettings() { return openAppSettings(); } + + @override + Future hasManageMediaPermission() { + return _permissionApi.hasManageMediaPermission(); + } + + @override + Future requestManageMediaPermission() { + return _permissionApi.requestManageMediaPermission(); + } + + @override + Future manageMediaPermission() { + return _permissionApi.manageMediaPermission(); + } } abstract interface class IPermissionRepository { @@ -42,4 +61,7 @@ abstract interface class IPermissionRepository { Future hasLocationAlwaysPermission(); Future requestLocationAlwaysPermission(); Future openSettings(); + Future hasManageMediaPermission(); + Future requestManageMediaPermission(); + Future manageMediaPermission(); } diff --git a/mobile/lib/repositories/widget.repository.dart b/mobile/lib/repositories/widget.repository.dart index f21d31e1ec..87209ac983 100644 --- a/mobile/lib/repositories/widget.repository.dart +++ b/mobile/lib/repositories/widget.repository.dart @@ -1,5 +1,6 @@ import 'package:home_widget/home_widget.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; final widgetRepositoryProvider = Provider((_) => const WidgetRepository()); @@ -14,7 +15,7 @@ class WidgetRepository { await HomeWidget.updateWidget(iOSName: iosName, qualifiedAndroidName: androidName); } - Future setAppGroupId(String appGroupId) async { - await HomeWidget.setAppGroupId(appGroupId); + Future setAppGroupId() async { + await HomeWidget.setAppGroupId(await networkApi.getAppGroupId()); } } diff --git a/mobile/lib/routing/router.dart b/mobile/lib/routing/router.dart index 1cc5faa733..b39a568e26 100644 --- a/mobile/lib/routing/router.dart +++ b/mobile/lib/routing/router.dart @@ -60,6 +60,7 @@ import 'package:immich_mobile/presentation/pages/drift_place_detail.page.dart'; import 'package:immich_mobile/presentation/pages/drift_recently_taken.page.dart'; import 'package:immich_mobile/presentation/pages/drift_recently_added.page.dart'; import 'package:immich_mobile/presentation/pages/drift_remote_album.page.dart'; +import 'package:immich_mobile/presentation/pages/drift_slideshow.page.dart'; import 'package:immich_mobile/presentation/pages/drift_trash.page.dart'; import 'package:immich_mobile/presentation/pages/drift_user_selection.page.dart'; import 'package:immich_mobile/presentation/pages/drift_video.page.dart'; @@ -189,6 +190,7 @@ class AppRouter extends RootStackRouter { AutoRoute(page: AssetTroubleshootRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: DownloadInfoRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: CleanupPreviewRoute.page, guards: [_authGuard, _duplicateGuard]), + AutoRoute(page: DriftSlideshowRoute.page, guards: [_authGuard, _duplicateGuard]), // required to handle all deeplinks in deep_link.service.dart // auto_route_library#1722 RedirectRoute(path: '*', redirectTo: '/'), diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart index 72054cf194..a4b538d789 100644 --- a/mobile/lib/routing/router.gr.dart +++ b/mobile/lib/routing/router.gr.dart @@ -1095,6 +1095,53 @@ class DriftSearchRoute extends PageRouteInfo { ); } +/// generated route for +/// [DriftSlideshowPage] +class DriftSlideshowRoute extends PageRouteInfo { + DriftSlideshowRoute({ + Key? key, + required TimelineService timeline, + List? children, + }) : super( + DriftSlideshowRoute.name, + args: DriftSlideshowRouteArgs(key: key, timeline: timeline), + initialChildren: children, + ); + + static const String name = 'DriftSlideshowRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return DriftSlideshowPage(key: args.key, timeline: args.timeline); + }, + ); +} + +class DriftSlideshowRouteArgs { + const DriftSlideshowRouteArgs({this.key, required this.timeline}); + + final Key? key; + + final TimelineService timeline; + + @override + String toString() { + return 'DriftSlideshowRouteArgs{key: $key, timeline: $timeline}'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other is! DriftSlideshowRouteArgs) return false; + return key == other.key && timeline == other.timeline; + } + + @override + int get hashCode => key.hashCode ^ timeline.hashCode; +} + /// generated route for /// [DriftTrashPage] class DriftTrashRoute extends PageRouteInfo { diff --git a/mobile/lib/services/action.service.dart b/mobile/lib/services/action.service.dart index 4e51c32f97..b9e601ca25 100644 --- a/mobile/lib/services/action.service.dart +++ b/mobile/lib/services/action.service.dart @@ -7,6 +7,7 @@ import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/asset_edit.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/services/tag.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; @@ -23,6 +24,7 @@ import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/utils/timezone.dart'; import 'package:immich_mobile/widgets/common/date_time_picker.dart'; import 'package:immich_mobile/widgets/common/location_picker.dart'; +import 'package:immich_mobile/widgets/common/tag_picker.dart'; import 'package:maplibre_gl/maplibre_gl.dart' as maplibre; final actionServiceProvider = Provider( @@ -35,6 +37,7 @@ final actionServiceProvider = Provider( ref.watch(trashedLocalAssetRepository), ref.watch(assetMediaRepositoryProvider), ref.watch(downloadRepositoryProvider), + ref.watch(tagServiceProvider), ), ); @@ -47,6 +50,7 @@ class ActionService { final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository; final AssetMediaRepository _assetMediaRepository; final DownloadRepository _downloadRepository; + final TagService _tagService; const ActionService( this._assetApiRepository, @@ -57,6 +61,7 @@ class ActionService { this._trashedLocalAssetRepository, this._assetMediaRepository, this._downloadRepository, + this._tagService, ); Future shareLink(List remoteIds, BuildContext context) async { @@ -234,6 +239,26 @@ class ActionService { return true; } + Future tagAssets(List remoteIds, BuildContext context) async { + final tagResults = await showTagPickerModal(context: context); + if (tagResults == null) { + // user cancelled + return null; + } + + final selectedTagIds = Set.from(tagResults.$1); + final selectedNewTagValues = tagResults.$2; + + if (selectedNewTagValues.isNotEmpty) { + final upsertedTags = await _tagService.upsertTags(selectedNewTagValues.toList()); + selectedTagIds.addAll(upsertedTags.map((t) => t.id)); + } + if (selectedTagIds.isEmpty) { + return 0; + } + return _tagService.bulkTagAssets(remoteIds, selectedTagIds.toList()); + } + Future stack(String userId, List remoteIds) async { final stack = await _assetApiRepository.stack(remoteIds); await _remoteAssetRepository.stack(userId, stack); diff --git a/mobile/lib/services/api.service.dart b/mobile/lib/services/api.service.dart index 33c87798a1..bc36c98768 100644 --- a/mobile/lib/services/api.service.dart +++ b/mobile/lib/services/api.service.dart @@ -5,8 +5,8 @@ import 'dart:io'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/network.repository.dart'; -import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart'; import 'package:immich_mobile/utils/debug_print.dart'; import 'package:immich_mobile/utils/url_helper.dart'; import 'package:logging/logging.dart'; @@ -177,30 +177,21 @@ class ApiService { if (serverEndpoint != null && serverEndpoint.isNotEmpty) { urls.add(serverEndpoint); } - final localEndpoint = Store.tryGet(StoreKey.localEndpoint); - if (localEndpoint != null && localEndpoint.isNotEmpty) { + final network = MetadataRepository.instance.systemConfig.network; + final localEndpoint = network.localEndpoint; + if (localEndpoint != null) { urls.add(localEndpoint); } - final externalJson = Store.tryGet(StoreKey.externalEndpointList); - if (externalJson != null) { - final List list = jsonDecode(externalJson); - for (final entry in list) { - final url = AuxilaryEndpoint.fromJson(entry).url; - if (url.isNotEmpty) { - urls.add(url); - } + for (final url in network.externalEndpointList) { + if (url.isNotEmpty) { + urls.add(url); } } return urls; } static Map getRequestHeaders() { - var customHeadersStr = Store.get(StoreKey.customHeaders, ""); - if (customHeadersStr.isEmpty) { - return const {}; - } - - return (jsonDecode(customHeadersStr) as Map).cast(); + return MetadataRepository.instance.systemConfig.network.customHeaders; } ApiClient get apiClient => _apiClient; diff --git a/mobile/lib/services/app_settings.service.dart b/mobile/lib/services/app_settings.service.dart index 1b9a38bc19..28bce32bb5 100644 --- a/mobile/lib/services/app_settings.service.dart +++ b/mobile/lib/services/app_settings.service.dart @@ -2,20 +2,10 @@ import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; enum AppSettingsEnum { - selectedAlbumSortOrder(StoreKey.selectedAlbumSortOrder, "selectedAlbumSortOrder", 2), advancedTroubleshooting(StoreKey.advancedTroubleshooting, null, false), manageLocalMediaAndroid(StoreKey.manageLocalMediaAndroid, null, false), - selectedAlbumSortReverse(StoreKey.selectedAlbumSortReverse, null, true), enableHapticFeedback(StoreKey.enableHapticFeedback, null, true), - syncAlbums(StoreKey.syncAlbums, null, false), - autoEndpointSwitching(StoreKey.autoEndpointSwitching, null, false), - enableBackup(StoreKey.enableBackup, null, false), - useCellularForUploadVideos(StoreKey.useWifiForUploadVideos, null, false), - useCellularForUploadPhotos(StoreKey.useWifiForUploadPhotos, null, false), - readonlyModeEnabled(StoreKey.readonlyModeEnabled, "readonlyModeEnabled", false), - albumGridView(StoreKey.albumGridView, "albumGridView", false), - backupRequireCharging(StoreKey.backupRequireCharging, null, false), - backupTriggerDelay(StoreKey.backupTriggerDelay, null, 30); + readonlyModeEnabled(StoreKey.readonlyModeEnabled, "readonlyModeEnabled", false); const AppSettingsEnum(this.storeKey, this.hiveKey, this.defaultValue); diff --git a/mobile/lib/services/auth.service.dart b/mobile/lib/services/auth.service.dart index 1b5eaab715..7d470ecd7a 100644 --- a/mobile/lib/services/auth.service.dart +++ b/mobile/lib/services/auth.service.dart @@ -1,19 +1,19 @@ import 'dart:async'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/metadata_key.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/utils/background_sync.dart'; import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/network.repository.dart'; import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart'; import 'package:immich_mobile/models/auth/login_response.model.dart'; import 'package:immich_mobile/providers/api.provider.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/repositories/auth.repository.dart'; import 'package:immich_mobile/repositories/auth_api.repository.dart'; import 'package:immich_mobile/services/api.service.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/network.service.dart'; import 'package:logging/logging.dart'; import 'package:openapi/api.dart'; @@ -25,7 +25,6 @@ final authServiceProvider = Provider( ref.watch(apiServiceProvider), ref.watch(networkServiceProvider), ref.watch(backgroundSyncProvider), - ref.watch(appSettingsServiceProvider), ), ); @@ -35,7 +34,6 @@ class AuthService { final ApiService _apiService; final NetworkService _networkService; final BackgroundSyncManager _backgroundSyncManager; - final AppSettingsService _appSettingsService; final _log = Logger("AuthService"); AuthService( @@ -44,7 +42,6 @@ class AuthService { this._apiService, this._networkService, this._backgroundSyncManager, - this._appSettingsService, ); /// Validates the provided server URL by resolving and setting the endpoint. @@ -103,7 +100,7 @@ class AuthService { _log.severe("Error clearing local data", error, stackTrace); }); - await _appSettingsService.setSetting(AppSettingsEnum.enableBackup, false); + await MetadataRepository.instance.write(MetadataKey.backupEnabled, false); } } @@ -123,10 +120,6 @@ class AuthService { _authRepository.clearLocalData(), Store.delete(StoreKey.currentUser), Store.delete(StoreKey.accessToken), - Store.delete(StoreKey.autoEndpointSwitching), - Store.delete(StoreKey.preferredWifiName), - Store.delete(StoreKey.localEndpoint), - Store.delete(StoreKey.externalEndpointList), ]); } diff --git a/mobile/lib/services/background_upload.service.dart b/mobile/lib/services/background_upload.service.dart index b76b9dcd61..37577e3666 100644 --- a/mobile/lib/services/background_upload.service.dart +++ b/mobile/lib/services/background_upload.service.dart @@ -13,14 +13,13 @@ import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/storage.provider.dart'; import 'package:immich_mobile/repositories/asset_media.repository.dart'; import 'package:immich_mobile/repositories/upload.repository.dart'; import 'package:immich_mobile/services/api.service.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/utils/debug_print.dart'; import 'package:logging/logging.dart'; import 'package:path/path.dart' as p; @@ -31,7 +30,6 @@ final backgroundUploadServiceProvider = Provider((ref) { ref.watch(storageRepositoryProvider), ref.watch(localAssetRepository), ref.watch(backupRepositoryProvider), - ref.watch(appSettingsServiceProvider), ref.watch(assetMediaRepositoryProvider), ); @@ -105,7 +103,6 @@ class BackgroundUploadService { this._storageRepository, this._localAssetRepository, this._backupRepository, - this._appSettingsService, this._assetMediaRepository, ) { _uploadRepository.onUploadStatus = _onUploadCallback; @@ -116,7 +113,6 @@ class BackgroundUploadService { final StorageRepository _storageRepository; final DriftLocalAssetRepository _localAssetRepository; final DriftBackupRepository _backupRepository; - final AppSettingsService _appSettingsService; final AssetMediaRepository _assetMediaRepository; final Logger _logger = Logger('BackgroundUploadService'); @@ -363,15 +359,14 @@ class BackgroundUploadService { } bool _shouldRequireWiFi(LocalAsset asset) { - bool requiresWiFi = true; - - if (asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadVideos)) { - requiresWiFi = false; - } else if (!asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadPhotos)) { - requiresWiFi = false; + final backup = MetadataRepository.instance.appConfig.backup; + if (asset.isVideo && backup.useCellularForVideos) { + return false; } - - return requiresWiFi; + if (!asset.isVideo && backup.useCellularForPhotos) { + return false; + } + return true; } Future buildUploadTask( diff --git a/mobile/lib/services/foreground_upload.service.dart b/mobile/lib/services/foreground_upload.service.dart index 6e7bee327a..81d453e5d2 100644 --- a/mobile/lib/services/foreground_upload.service.dart +++ b/mobile/lib/services/foreground_upload.service.dart @@ -7,18 +7,17 @@ import 'package:immich_mobile/domain/models/asset/asset_metadata.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/extensions/network_capability_extensions.dart'; +import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; import 'package:immich_mobile/platform/connectivity_api.g.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; import 'package:immich_mobile/providers/infrastructure/storage.provider.dart'; import 'package:immich_mobile/repositories/asset_media.repository.dart'; import 'package:immich_mobile/repositories/upload.repository.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:logging/logging.dart'; import 'package:path/path.dart' as p; import 'package:photo_manager/photo_manager.dart' show PMProgressHandler; @@ -39,7 +38,6 @@ final foregroundUploadServiceProvider = Provider((ref) { ref.watch(storageRepositoryProvider), ref.watch(backupRepositoryProvider), ref.watch(connectivityApiProvider), - ref.watch(appSettingsServiceProvider), ref.watch(assetMediaRepositoryProvider), ); }); @@ -55,7 +53,6 @@ class ForegroundUploadService { this._storageRepository, this._backupRepository, this._connectivityApi, - this._appSettingsService, this._assetMediaRepository, ); @@ -63,7 +60,6 @@ class ForegroundUploadService { final StorageRepository _storageRepository; final DriftBackupRepository _backupRepository; final ConnectivityApi _connectivityApi; - final AppSettingsService _appSettingsService; final AssetMediaRepository _assetMediaRepository; final Logger _logger = Logger('ForegroundUploadService'); @@ -455,14 +451,13 @@ class ForegroundUploadService { } bool _shouldRequireWiFi(LocalAsset asset) { - bool requiresWiFi = true; - - if (asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadVideos)) { - requiresWiFi = false; - } else if (!asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadPhotos)) { - requiresWiFi = false; + final backup = MetadataRepository.instance.appConfig.backup; + if (asset.isVideo && backup.useCellularForVideos) { + return false; } - - return requiresWiFi; + if (!asset.isVideo && backup.useCellularForPhotos) { + return false; + } + return true; } } diff --git a/mobile/lib/services/local_files_manager.service.dart b/mobile/lib/services/local_files_manager.service.dart deleted file mode 100644 index 0cc00f3e4b..0000000000 --- a/mobile/lib/services/local_files_manager.service.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'package:flutter/services.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:logging/logging.dart'; - -final localFileManagerServiceProvider = Provider((ref) => const LocalFilesManagerService()); - -class LocalFilesManagerService { - const LocalFilesManagerService(); - - static final Logger _logger = Logger('LocalFilesManager'); - static const MethodChannel _channel = MethodChannel('file_trash'); - - Future moveToTrash(List mediaUrls) async { - try { - return await _channel.invokeMethod('moveToTrash', {'mediaUrls': mediaUrls}); - } catch (e, s) { - _logger.warning('Error moving file to trash', e, s); - return false; - } - } - - Future restoreFromTrash(String fileName, int type) async { - try { - return await _channel.invokeMethod('restoreFromTrash', {'fileName': fileName, 'type': type}); - } catch (e, s) { - _logger.warning('Error restore file from trash', e, s); - return false; - } - } - - Future restoreFromTrashById(String mediaId, int type) async { - try { - return await _channel.invokeMethod('restoreFromTrash', {'mediaId': mediaId, 'type': type}); - } catch (e, s) { - _logger.warning('Error restore file from trash by Id', e, s); - return false; - } - } - - Future requestManageMediaPermission() async { - try { - return await _channel.invokeMethod('requestManageMediaPermission'); - } catch (e, s) { - _logger.warning('Error requesting manage media permission', e, s); - return false; - } - } - - Future hasManageMediaPermission() async { - try { - return await _channel.invokeMethod('hasManageMediaPermission'); - } catch (e, s) { - _logger.warning('Error requesting manage media permission state', e, s); - return false; - } - } - - Future manageMediaPermission() async { - try { - return await _channel.invokeMethod('manageMediaPermission'); - } catch (e, s) { - _logger.warning('Error requesting manage media permission settings', e, s); - return false; - } - } -} diff --git a/mobile/lib/services/widget.service.dart b/mobile/lib/services/widget.service.dart index 23ec0aa770..91f65dd684 100644 --- a/mobile/lib/services/widget.service.dart +++ b/mobile/lib/services/widget.service.dart @@ -12,7 +12,7 @@ class WidgetService { const WidgetService(this._repository); Future writeCredentials(String serverURL, String sessionKey, String? customHeaders) async { - await _repository.setAppGroupId(appShareGroupId); + await _repository.setAppGroupId(); await _repository.saveData(kWidgetServerEndpoint, serverURL); await _repository.saveData(kWidgetAuthToken, sessionKey); @@ -25,7 +25,7 @@ class WidgetService { } Future clearCredentials() async { - await _repository.setAppGroupId(appShareGroupId); + await _repository.setAppGroupId(); await _repository.saveData(kWidgetServerEndpoint, ""); await _repository.saveData(kWidgetAuthToken, ""); await _repository.saveData(kWidgetCustomHeaders, ""); diff --git a/mobile/lib/utils/action_button.utils.dart b/mobile/lib/utils/action_button.utils.dart index c38c536136..b9cff613fd 100644 --- a/mobile/lib/utils/action_button.utils.dart +++ b/mobile/lib/utils/action_button.utils.dart @@ -27,6 +27,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/set_profile_pi import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/similar_photos_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/slideshow_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/unarchive_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart'; @@ -73,6 +74,7 @@ enum ActionButtonType { similarPhotos, setProfilePicture, viewInTimeline, + slideshow, download, upload, openInBrowser, @@ -123,9 +125,8 @@ enum ActionButtonType { context.timelineOrigin == TimelineOrigin.trash, ActionButtonType.deletePermanent => context.isOwner && // - context.asset.hasRemote && // - !context.isTrashEnabled || - context.isInLockedView, + context.asset.hasRemote && // + (!context.isTrashEnabled || context.timelineOrigin == TimelineOrigin.trash || context.isInLockedView), ActionButtonType.delete => context.isOwner && // !context.isInLockedView && // @@ -180,6 +181,7 @@ enum ActionButtonType { context.timelineOrigin != TimelineOrigin.localAlbum && context.isOwner, ActionButtonType.cast => context.isCasting || context.asset.hasRemote, + ActionButtonType.slideshow => true, }; } @@ -201,6 +203,7 @@ enum ActionButtonType { iconOnly: iconOnly, menuItem: menuItem, ), + ActionButtonType.slideshow => SlideshowActionButton(iconOnly: iconOnly, menuItem: menuItem), ActionButtonType.archive => ArchiveActionButton(source: context.source, iconOnly: iconOnly, menuItem: menuItem), ActionButtonType.unarchive => UnArchiveActionButton( source: context.source, @@ -324,13 +327,14 @@ class ActionButtonBuilder { ActionButtonType.archive, ActionButtonType.unarchive, ActionButtonType.restoreTrash, + ActionButtonType.deletePermanent, }; static List build(ActionButtonContext context) { return _actionTypes.where((type) => type.shouldShow(context)).map((type) => type.buildButton(context)).toList(); } - static List buildViewerKebabMenu(ActionButtonContext context, BuildContext buildContext) { + static List buildViewerKebabMenu(ActionButtonContext context, BuildContext buildContext, WidgetRef ref) { final visibleButtons = defaultViewerKebabMenuOrder .where((type) => !defaultViewerBottomBarButtons.contains(type) && type.shouldShow(context)) .toList(); @@ -346,7 +350,7 @@ class ActionButtonBuilder { if (lastGroup != null && type.kebabMenuGroup != lastGroup) { result.add(const Divider(height: 1)); } - result.add(type.buildButton(context, buildContext, false, true)); + result.add(type.buildButton(context, buildContext, false, true).build(buildContext, ref)); lastGroup = type.kebabMenuGroup; } diff --git a/mobile/lib/utils/migration.dart b/mobile/lib/utils/migration.dart index 8f0eb00b16..41301fb227 100644 --- a/mobile/lib/utils/migration.dart +++ b/mobile/lib/utils/migration.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'package:drift/drift.dart'; import 'package:flutter/material.dart'; @@ -12,7 +13,8 @@ import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/entities/metadata.entity.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/network.repository.dart'; -import 'package:immich_mobile/services/api.service.dart'; +import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart'; +import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; const int targetVersion = 26; @@ -37,12 +39,35 @@ Future _migrateTo25() async { return; } - final serverUrls = ApiService.getServerUrls(); - if (serverUrls.isEmpty) { + final urls = []; + final serverEndpoint = Store.tryGet(StoreKey.serverEndpoint); + if (serverEndpoint != null && serverEndpoint.isNotEmpty) { + urls.add(serverEndpoint); + } + final localEndpoint = Store.tryGet(StoreKey.legacyLocalEndpoint); + if (localEndpoint != null && localEndpoint.isNotEmpty) { + urls.add(localEndpoint); + } + final externalJson = Store.tryGet(StoreKey.legacyExternalEndpointList); + if (externalJson != null) { + final List list = jsonDecode(externalJson); + for (final entry in list) { + final url = AuxilaryEndpoint.fromJson(entry).url; + if (url.isNotEmpty) { + urls.add(url); + } + } + } + if (urls.isEmpty) { return; } - await NetworkRepository.setHeaders(ApiService.getRequestHeaders(), serverUrls, token: accessToken); + final customHeadersStr = Store.get(StoreKey.legacyCustomHeaders, ""); + final headers = customHeadersStr.isEmpty + ? const {} + : (jsonDecode(customHeadersStr) as Map).cast(); + + await NetworkRepository.setHeaders(headers, urls, token: accessToken); } Future _migrateTo26(Drift drift) async { @@ -57,14 +82,7 @@ Future _migrateTo26(Drift drift) async { final cleanupKeepAlbumIds = await migrator.readLegacyStoreString(StoreKey.legacyCleanupKeepAlbumIds.id); if (cleanupKeepAlbumIds != null) { final ids = cleanupKeepAlbumIds.split(',').where((id) => id.isNotEmpty).toList(); - await drift.metadataEntity.insertOnConflictUpdate( - MetadataEntityCompanion.insert( - key: MetadataKey.cleanupKeepAlbumIds.key, - value: MetadataKey.cleanupKeepAlbumIds.encode(ids), - updatedAt: Value(DateTime.now()), - ), - ); - await migrator.deleteLegacyStoreRows([StoreKey.legacyCleanupKeepAlbumIds.id]); + migrator.stage(StoreKey.legacyCleanupKeepAlbumIds, MetadataKey.cleanupKeepAlbumIds, ids); } await migrator.migrateBool(StoreKey.legacyCleanupKeepFavorites, MetadataKey.cleanupKeepFavorites); await migrator.migrateEnumIndex( @@ -96,9 +114,87 @@ Future _migrateTo26(Drift drift) async { await migrator.migrateBool(StoreKey.legacyLoadOriginalVideo, MetadataKey.viewerLoadOriginalVideo); await migrator.migrateBool(StoreKey.legacyAutoPlayVideo, MetadataKey.viewerAutoPlayVideo); await migrator.migrateBool(StoreKey.legacyTapToNavigate, MetadataKey.viewerTapToNavigate); + // Network + await migrator.migrateBool(StoreKey.legacyAutoEndpointSwitching, MetadataKey.networkAutoEndpointSwitching); + await migrator.migrateString(StoreKey.legacyPreferredWifiName, MetadataKey.networkPreferredWifiName); + await migrator.migrateString(StoreKey.legacyLocalEndpoint, MetadataKey.networkLocalEndpoint); + await _migrateExternalEndpointList(migrator); + await _migrateCustomHeaders(migrator); + // Album + await _migrateAlbumSortMode(migrator); + await migrator.migrateBool(StoreKey.legacySelectedAlbumSortReverse, MetadataKey.albumIsReverse); + await migrator.migrateBool(StoreKey.legacyAlbumGridView, MetadataKey.albumIsGrid); + // Backup + await migrator.migrateBool(StoreKey.legacyEnableBackup, MetadataKey.backupEnabled); + await migrator.migrateBool(StoreKey.legacyUseWifiForUploadVideos, MetadataKey.backupUseCellularForVideos); + await migrator.migrateBool(StoreKey.legacyUseWifiForUploadPhotos, MetadataKey.backupUseCellularForPhotos); + await migrator.migrateBool(StoreKey.legacyBackupRequireCharging, MetadataKey.backupRequireCharging); + await migrator.migrateInt(StoreKey.legacyBackupTriggerDelay, MetadataKey.backupTriggerDelay); + await migrator.migrateBool(StoreKey.legacySyncAlbums, MetadataKey.backupSyncAlbums); await migrator.complete(); } +Future _migrateAlbumSortMode(_StoreMigrator migrator) async { + final raw = await migrator.readLegacyStoreInt(StoreKey.legacySelectedAlbumSortOrder.id); + if (raw == null) { + return; + } + + final mode = AlbumSortMode.values.firstWhere( + (e) => e.storeIndex == raw, + orElse: () => MetadataKey.albumSortMode.defaultValue, + ); + + migrator.stage(StoreKey.legacySelectedAlbumSortOrder, MetadataKey.albumSortMode, mode); +} + +Future _migrateExternalEndpointList(_StoreMigrator migrator) async { + final raw = await migrator.readLegacyStoreString(StoreKey.legacyExternalEndpointList.id); + if (raw == null) { + return; + } + + final urls = []; + try { + final decoded = jsonDecode(raw); + if (decoded is List) { + for (final entry in decoded) { + final url = AuxilaryEndpoint.fromJson(entry).url; + if (url.isNotEmpty) { + urls.add(url); + } + } + } + } on FormatException { + // ignore invalid entries + } + + migrator.stage(StoreKey.legacyExternalEndpointList, MetadataKey.networkExternalEndpointList, urls); +} + +Future _migrateCustomHeaders(_StoreMigrator migrator) async { + final raw = await migrator.readLegacyStoreString(StoreKey.legacyCustomHeaders.id); + if (raw == null) { + return; + } + + final headers = {}; + try { + final decoded = jsonDecode(raw); + if (decoded is Map) { + decoded.forEach((key, value) { + if (key is String && value is String) { + headers[key] = value; + } + }); + } + } on FormatException { + // ignore invalid entries + } + + migrator.stage(StoreKey.legacyCustomHeaders, MetadataKey.networkCustomHeaders, headers); +} + class _StoreMigrator { final Drift _db; final Map, Object> _cache = {}; @@ -153,6 +249,21 @@ class _StoreMigrator { _migratedStoreIds.add(legacyKey.id); } + Future migrateString(StoreKey legacyKey, MetadataKey newKey) async { + final value = await readLegacyStoreString(legacyKey.id); + if (value == null) { + return; + } + + _cache[newKey] = value; + _migratedStoreIds.add(legacyKey.id); + } + + void stage(StoreKey legacyKey, MetadataKey newKey, T value) { + _cache[newKey] = value; + _migratedStoreIds.add(legacyKey.id); + } + Future complete() async { await _db.batch((batch) { for (final entry in _cache.entries) { diff --git a/mobile/lib/utils/url_helper.dart b/mobile/lib/utils/url_helper.dart index fc3b4bbb3f..b7dc41c4cf 100644 --- a/mobile/lib/utils/url_helper.dart +++ b/mobile/lib/utils/url_helper.dart @@ -24,6 +24,17 @@ String? getServerUrl() { ); } +String? buildSharedLinkUrl({required String? baseUrl, required String key, String? slug}) { + if (baseUrl == null || baseUrl.isEmpty) { + return null; + } + + final normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl : '$baseUrl/'; + final path = (slug != null && slug.isNotEmpty) ? 's/$slug' : 'share/$key'; + + return '$normalizedBaseUrl$path'; +} + /// Converts a Unicode URL to its ASCII-compatible encoding (Punycode). /// /// This is especially useful for internationalized domain names (IDNs), diff --git a/mobile/lib/widgets/common/immich_sliver_app_bar.dart b/mobile/lib/widgets/common/immich_sliver_app_bar.dart index cb429c9f48..32aa766dec 100644 --- a/mobile/lib/widgets/common/immich_sliver_app_bar.dart +++ b/mobile/lib/widgets/common/immich_sliver_app_bar.dart @@ -6,13 +6,12 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/setting.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/models/server_info/server_info.model.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; import 'package:immich_mobile/providers/cast.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart'; import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/sync_status.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; @@ -193,64 +192,51 @@ class _BackupIndicator extends ConsumerWidget { } Widget? _getBackupBadgeIcon(BuildContext context, WidgetRef ref) { - final backupStateStream = ref.watch(settingsProvider).watch(Setting.enableBackup); + final backupEnabled = ref.watch(appConfigProvider.select((c) => c.backup.enabled)); final hasError = ref.watch(driftBackupProvider.select((state) => state.error != BackupError.none)); final isDarkTheme = context.isDarkTheme; final iconColor = isDarkTheme ? Colors.white : Colors.black; final isUploading = ref.watch(driftBackupProvider.select((state) => state.uploadItems.isNotEmpty)); - return StreamBuilder( - stream: backupStateStream, - initialData: false, - builder: (ctx, snapshot) { - final backupEnabled = snapshot.data ?? false; + if (!backupEnabled) { + return _BadgeLabel( + Icon(Icons.cloud_off_rounded, size: 9, color: iconColor, semanticLabel: 'backup_controller_page_backup'.tr()), + ); + } - if (!backupEnabled) { - return _BadgeLabel( - Icon( - Icons.cloud_off_rounded, - size: 9, - color: iconColor, - semanticLabel: 'backup_controller_page_backup'.tr(), + if (hasError) { + return _BadgeLabel( + Icon( + Icons.warning_rounded, + size: 12, + color: context.colorScheme.error, + semanticLabel: 'backup_controller_page_backup'.tr(), + ), + backgroundColor: context.colorScheme.errorContainer, + ); + } + + if (isUploading) { + return _BadgeLabel( + Container( + padding: const EdgeInsets.all(3.5), + child: Theme( + data: context.themeData.copyWith( + progressIndicatorTheme: context.themeData.progressIndicatorTheme.copyWith(year2023: true), ), - ); - } - - if (hasError) { - return _BadgeLabel( - Icon( - Icons.warning_rounded, - size: 12, - color: context.colorScheme.error, - semanticLabel: 'backup_controller_page_backup'.tr(), + child: CircularProgressIndicator( + strokeWidth: 2, + strokeCap: StrokeCap.round, + valueColor: AlwaysStoppedAnimation(iconColor), + semanticsLabel: 'backup_controller_page_backup'.tr(), ), - backgroundColor: context.colorScheme.errorContainer, - ); - } + ), + ), + ); + } - if (isUploading) { - return _BadgeLabel( - Container( - padding: const EdgeInsets.all(3.5), - child: Theme( - data: context.themeData.copyWith( - progressIndicatorTheme: context.themeData.progressIndicatorTheme.copyWith(year2023: true), - ), - child: CircularProgressIndicator( - strokeWidth: 2, - strokeCap: StrokeCap.round, - valueColor: AlwaysStoppedAnimation(iconColor), - semanticsLabel: 'backup_controller_page_backup'.tr(), - ), - ), - ), - ); - } - - return _BadgeLabel( - Icon(Icons.check_outlined, size: 9, color: iconColor, semanticLabel: 'backup_controller_page_backup'.tr()), - ); - }, + return _BadgeLabel( + Icon(Icons.check_outlined, size: 9, color: iconColor, semanticLabel: 'backup_controller_page_backup'.tr()), ); } } diff --git a/mobile/lib/widgets/common/remote_album_sliver_app_bar.dart b/mobile/lib/widgets/common/remote_album_sliver_app_bar.dart index 50746f5cbd..2fc136302d 100644 --- a/mobile/lib/widgets/common/remote_album_sliver_app_bar.dart +++ b/mobile/lib/widgets/common/remote_album_sliver_app_bar.dart @@ -18,6 +18,7 @@ import 'package:immich_mobile/providers/infrastructure/current_album.provider.da import 'package:immich_mobile/providers/infrastructure/remote_album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/widgets/album/remote_album_shared_user_icons.dart'; class RemoteAlbumSliverAppBar extends ConsumerStatefulWidget { @@ -89,6 +90,10 @@ class _MesmerizingSliverAppBarState extends ConsumerState context.maybePop(), ), actions: [ + IconButton( + onPressed: () => context.pushRoute(DriftSlideshowRoute(timeline: ref.read(timelineServiceProvider))), + icon: Icon(Icons.slideshow_outlined, color: actionIconColor, shadows: actionIconShadows), + ), if (currentAlbum.isActivityEnabled && currentAlbum.isShared) IconButton( icon: Icon(Icons.chat_outlined, color: actionIconColor, shadows: actionIconShadows), diff --git a/mobile/lib/widgets/common/tag_picker.dart b/mobile/lib/widgets/common/tag_picker.dart index 0ab25d14cb..0265cf7e6c 100644 --- a/mobile/lib/widgets/common/tag_picker.dart +++ b/mobile/lib/widgets/common/tag_picker.dart @@ -8,12 +8,78 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/providers/infrastructure/tag.provider.dart'; import 'package:immich_mobile/widgets/common/search_field.dart'; -class TagPicker extends HookConsumerWidget { - const TagPicker({super.key, required this.onSelect, required this.filter}); +String _trimSlashes(String s) => s.replaceAll(RegExp(r'^/+|/+$'), ''); + +Future<(Set, Set)?> showTagPickerModal({required BuildContext context, Set? initialSelection}) { + return showDialog<(Set, Set)?>( + context: context, + builder: (context) => _TagPickerModal(initialSelection: initialSelection), + ); +} + +class _TagPickerModal extends HookConsumerWidget { + final Set? initialSelection; + + const _TagPickerModal({this.initialSelection}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final selectedTagIds = useState>(initialSelection ?? {}); + final newTagValues = useState>({}); + + void onSelectExistingTag(Iterable tags) { + selectedTagIds.value = tags.map((tag) => tag.id).toSet(); + } + + void onSelectNewTag(Set tags) { + newTagValues.value = tags; + } + + return AlertDialog( + contentPadding: const EdgeInsets.symmetric(vertical: 16, horizontal: 0), + actions: [ + TextButton( + onPressed: () => context.pop(), + child: Text( + "cancel", + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w600, + color: context.colorScheme.error, + ), + ).tr(), + ), + TextButton( + onPressed: () => context.pop((selectedTagIds.value, newTagValues.value)), + child: Text( + "action_common_update", + style: context.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600, color: context.primaryColor), + ).tr(), + ), + ], + content: SizedBox( + width: MediaQuery.of(context).size.width * 0.8, + height: MediaQuery.of(context).size.height * 0.6, + child: TagPicker( + onSelectExistingTag: onSelectExistingTag, + filter: selectedTagIds.value, + onSelectNewTag: onSelectNewTag, + ), + ), + ); + } +} + +class TagPicker extends HookConsumerWidget { + const TagPicker({super.key, required this.onSelectExistingTag, required this.filter, this.onSelectNewTag}); - final Function(Iterable) onSelect; final Set filter; + /// Callback when existing tags are selected/deselected. + final Function(Iterable) onSelectExistingTag; + + /// If not null, shows a tile to create a new tag with user's filter input. + final Function(Set)? onSelectNewTag; + @override Widget build(BuildContext context, WidgetRef ref) { final formFocus = useFocusNode(); @@ -21,6 +87,7 @@ class TagPicker extends HookConsumerWidget { final tags = ref.watch(tagProvider); final selectedTagIds = useState>(filter); final borderRadius = const BorderRadius.all(Radius.circular(10)); + final selectedNewTagValues = useState>({}); return Column( children: [ @@ -41,13 +108,53 @@ class TagPicker extends HookConsumerWidget { Expanded( child: tags.widgetWhen( onData: (tags) { + final trimmedQuery = _trimSlashes(searchQuery.value); final queryResult = tags - .where((t) => t.value.toLowerCase().contains(searchQuery.value.toLowerCase())) + .where((t) => t.value.toLowerCase().contains(trimmedQuery.toLowerCase())) .toList(); + final showCreateTile = + (onSelectNewTag != null) && + trimmedQuery.isNotEmpty && + !tags.any((t) => t.value.toLowerCase() == trimmedQuery.toLowerCase()); + final isCreateSelected = selectedNewTagValues.value.contains(trimmedQuery); return ListView.builder( - itemCount: queryResult.length, + itemCount: queryResult.length + (showCreateTile ? 1 : 0), padding: const EdgeInsets.all(8), itemBuilder: (context, index) { + if (showCreateTile && index == queryResult.length) { + // Create new tag tile + return Padding( + padding: const EdgeInsets.only(bottom: 2.0), + child: Container( + decoration: BoxDecoration( + color: isCreateSelected ? context.primaryColor : context.primaryColor.withAlpha(25), + borderRadius: const BorderRadius.all(Radius.circular(10)), + ), + child: ListTile( + title: Text( + trimmedQuery, + style: context.textTheme.bodyLarge?.copyWith( + color: isCreateSelected ? context.colorScheme.onPrimary : context.colorScheme.onSurface, + ), + ), + trailing: Icon( + Icons.add, + color: isCreateSelected ? context.colorScheme.onPrimary : context.colorScheme.onSurface, + ), + onTap: () { + final newSelectedNewTagValues = {...selectedNewTagValues.value}; + if (isCreateSelected) { + newSelectedNewTagValues.remove(trimmedQuery); + } else { + newSelectedNewTagValues.add(trimmedQuery); + } + selectedNewTagValues.value = newSelectedNewTagValues; + onSelectNewTag!.call(newSelectedNewTagValues); + }, + ), + ), + ); + } final tag = queryResult[index]; final isSelected = selectedTagIds.value.any((id) => id == tag.id); @@ -73,7 +180,7 @@ class TagPicker extends HookConsumerWidget { newSelected.add(tag.id); } selectedTagIds.value = newSelected; - onSelect(tags.where((t) => newSelected.contains(t.id))); + onSelectExistingTag(tags.where((t) => newSelected.contains(t.id))); }, ), ), diff --git a/mobile/lib/widgets/forms/login/login_form.dart b/mobile/lib/widgets/forms/login/login_form.dart index ced395979e..a453a47f7e 100644 --- a/mobile/lib/widgets/forms/login/login_form.dart +++ b/mobile/lib/widgets/forms/login/login_form.dart @@ -15,6 +15,7 @@ import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/gallery_permission.provider.dart'; @@ -22,7 +23,7 @@ import 'package:immich_mobile/providers/oauth.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/view_intent/view_intent_handler.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart'; -import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; +import 'package:immich_mobile/repositories/permission.repository.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/utils/provider_utils.dart'; import 'package:immich_mobile/utils/url_helper.dart'; @@ -189,13 +190,13 @@ class LoginForm extends HookConsumerWidget { await viewIntentHandler.flushDeferredViewIntent(); await backgroundManager.hashAssets(); - if (Store.get(StoreKey.syncAlbums, false)) { + if (MetadataRepository.instance.appConfig.backup.syncAlbums) { await backgroundManager.syncLinkedAlbum(); } } getManageMediaPermission() async { - final hasPermission = await ref.read(localFilesManagerRepositoryProvider).hasManageMediaPermission(); + final hasPermission = await ref.read(permissionRepositoryProvider).hasManageMediaPermission(); if (!hasPermission) { await showDialog( context: context, @@ -226,7 +227,7 @@ class LoginForm extends HookConsumerWidget { ), TextButton( onPressed: () { - ref.read(localFilesManagerRepositoryProvider).requestManageMediaPermission(); + unawaited(ref.read(permissionRepositoryProvider).requestManageMediaPermission()); Navigator.of(context).pop(); }, child: Text( @@ -400,19 +401,16 @@ class LoginForm extends HookConsumerWidget { mainAxisSize: MainAxisSize.max, children: [ ImmichForm( + onSubmit: getServerAuthSettings, submitText: 'next'.t(context: context), submitIcon: Icons.arrow_forward_rounded, - onSubmit: getServerAuthSettings, - child: ImmichTextInput( + builder: (_, form) => ImmichURLInput( controller: serverEndpointController, label: 'login_form_endpoint_url'.t(context: context), hintText: 'login_form_endpoint_hint'.t(context: context), validator: _validateUrl, - keyboardAction: TextInputAction.next, - keyboardType: TextInputType.url, - autofillHints: const [AutofillHints.url], - autoCorrect: false, - onSubmit: (ctx, _) => ImmichForm.of(ctx).submit(), + keyboardAction: .next, + onSubmit: (_) => form.submit(), ), ), ImmichTextButton( @@ -440,10 +438,10 @@ class LoginForm extends HookConsumerWidget { ), if (isPasswordLoginEnable.value) ImmichForm( + onSubmit: login, submitText: 'login'.t(context: context), submitIcon: Icons.login_rounded, - onSubmit: login, - child: Column( + builder: (context, form) => Column( spacing: ImmichSpacing.md, children: [ ImmichTextInput( @@ -454,7 +452,7 @@ class LoginForm extends HookConsumerWidget { keyboardAction: TextInputAction.next, keyboardType: TextInputType.emailAddress, autofillHints: const [AutofillHints.email], - onSubmit: (_, _) => passwordFocusNode.requestFocus(), + onSubmit: (_) => passwordFocusNode.requestFocus(), ), ImmichPasswordInput( controller: passwordController, @@ -462,17 +460,17 @@ class LoginForm extends HookConsumerWidget { label: 'password'.t(context: context), hintText: 'login_form_password_hint'.t(context: context), keyboardAction: TextInputAction.go, - onSubmit: (ctx, _) => ImmichForm.of(ctx).submit(), + onSubmit: (_) => form.submit(), ), ], ), ), if (isOauthEnable.value) ImmichForm( + onSubmit: oAuthLogin, submitText: oAuthButtonLabel.value, submitIcon: Icons.pin_outlined, - onSubmit: oAuthLogin, - child: isPasswordLoginEnable.value + builder: (context, _) => isPasswordLoginEnable.value ? Padding( padding: const EdgeInsets.only(left: 18.0, right: 18.0, top: 12.0), child: Divider(color: context.isDarkTheme ? Colors.white : Colors.black, height: 5), diff --git a/mobile/lib/widgets/settings/advanced_settings.dart b/mobile/lib/widgets/settings/advanced_settings.dart index 60557aaaca..5de2570737 100644 --- a/mobile/lib/widgets/settings/advanced_settings.dart +++ b/mobile/lib/widgets/settings/advanced_settings.dart @@ -10,7 +10,7 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; -import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; +import 'package:immich_mobile/repositories/permission.repository.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/utils/bytes_units.dart'; import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; @@ -57,9 +57,7 @@ class AdvancedSettings extends HookConsumerWidget { () async { isManageMediaSupported.value = await checkAndroidVersion(); if (isManageMediaSupported.value) { - manageMediaAndroidPermission.value = await ref - .read(localFilesManagerRepositoryProvider) - .hasManageMediaPermission(); + manageMediaAndroidPermission.value = await ref.read(permissionRepositoryProvider).hasManageMediaPermission(); } }(); return null; @@ -82,7 +80,7 @@ class AdvancedSettings extends HookConsumerWidget { subtitle: "advanced_settings_sync_remote_deletions_subtitle".tr(), onChanged: (value) async { if (value) { - final result = await ref.read(localFilesManagerRepositoryProvider).requestManageMediaPermission(); + final result = await ref.read(permissionRepositoryProvider).requestManageMediaPermission(); manageLocalMediaAndroid.value = result; manageMediaAndroidPermission.value = result; } @@ -96,7 +94,7 @@ class AdvancedSettings extends HookConsumerWidget { ? const Color.fromARGB(255, 243, 188, 106) : null, onActionTap: () async { - final result = await ref.read(localFilesManagerRepositoryProvider).manageMediaPermission(); + final result = await ref.read(permissionRepositoryProvider).manageMediaPermission(); manageMediaAndroidPermission.value = result; }, ), diff --git a/mobile/lib/widgets/settings/asset_viewer_settings/asset_viewer_settings.dart b/mobile/lib/widgets/settings/asset_viewer_settings/asset_viewer_settings.dart index a2bca2745f..f3b9039b2b 100644 --- a/mobile/lib/widgets/settings/asset_viewer_settings/asset_viewer_settings.dart +++ b/mobile/lib/widgets/settings/asset_viewer_settings/asset_viewer_settings.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:immich_mobile/widgets/settings/asset_viewer_settings/image_viewer_quality_setting.dart'; import 'package:immich_mobile/widgets/settings/asset_viewer_settings/image_viewer_tap_to_navigate_setting.dart'; import 'package:immich_mobile/widgets/settings/asset_viewer_settings/video_viewer_settings.dart'; +import 'package:immich_mobile/widgets/settings/asset_viewer_settings/slideshow_settings.dart'; import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart'; class AssetViewerSettings extends StatelessWidget { @@ -13,6 +14,7 @@ class AssetViewerSettings extends StatelessWidget { const ImageViewerQualitySetting(), const ImageViewerTapToNavigateSetting(), const VideoViewerSettings(), + const SlideshowSettings(), ]; return SettingsSubPageScaffold(settings: assetViewerSetting, showDivider: true); diff --git a/mobile/lib/widgets/settings/asset_viewer_settings/slideshow_settings.dart b/mobile/lib/widgets/settings/asset_viewer_settings/slideshow_settings.dart new file mode 100644 index 0000000000..4e566e6065 --- /dev/null +++ b/mobile/lib/widgets/settings/asset_viewer_settings/slideshow_settings.dart @@ -0,0 +1,123 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart'; +import 'package:immich_mobile/widgets/settings/setting_group_title.dart'; +import 'package:immich_mobile/widgets/settings/settings_radio_list_tile.dart'; +import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart'; +import 'package:immich_mobile/widgets/settings/settings_sub_title.dart'; +import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; + +class SlideshowSettings extends HookConsumerWidget { + const SlideshowSettings({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final slideshow = ref.read(appConfigProvider).slideshow; + final useTransition = useState(slideshow.transition); + final useRepeat = useState(slideshow.repeat); + final useDuration = useState(slideshow.duration); + final useLook = useState(slideshow.look); + final useDirection = useState(slideshow.direction); + + useValueChanged(useTransition.value, (_, __) { + ref.read(metadataProvider).write(.slideshowTransition, useTransition.value); + }); + useValueChanged(useRepeat.value, (_, __) { + ref.read(metadataProvider).write(.slideshowRepeat, useRepeat.value); + }); + useValueChanged(useDuration.value, (_, __) { + ref.read(metadataProvider).write(.slideshowDuration, useDuration.value); + }); + useValueChanged(useLook.value, (_, __) { + ref.read(metadataProvider).write(.slideshowLook, useLook.value); + }); + useValueChanged(useDirection.value, (_, __) { + ref.read(metadataProvider).write(.slideshowDirection, useDirection.value); + }); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SettingGroupTitle( + title: 'slideshow'.t(context: context), + icon: Icons.slideshow_outlined, + ), + SettingsSwitchListTile( + valueNotifier: useTransition, + title: "show_slideshow_transition".t(context: context), + enabled: useDirection.value != SlideshowDirection.shuffle, + ), + SettingsSwitchListTile( + valueNotifier: useRepeat, + title: "slideshow_repeat".t(context: context), + subtitle: "slideshow_repeat_description".t(context: context), + ), + SettingsSliderListTile( + valueNotifier: useDuration, + text: "duration".t(context: context), + minValue: 5, + noDivisons: 5, + maxValue: 30, + ), + Padding( + padding: const EdgeInsets.only(top: 20), + child: SettingsSubTitle(title: 'look'.t(context: context)), + ), + SettingsRadioListTile( + groups: [ + SettingsRadioGroup( + title: 'contain'.t(context: context), + value: SlideshowLook.contain, + ), + SettingsRadioGroup( + title: 'cover'.t(context: context), + value: SlideshowLook.cover, + ), + SettingsRadioGroup( + title: 'blurred_background'.t(context: context), + value: SlideshowLook.blurredBackground, + ), + ], + groupBy: useLook.value, + onRadioChanged: (value) { + if (value != null) { + useLook.value = value; + } + }, + ), + Padding( + padding: const EdgeInsets.only(top: 20), + child: SettingsSubTitle(title: 'direction'.t(context: context)), + ), + Padding( + padding: const EdgeInsets.only(bottom: 32), + child: SettingsRadioListTile( + groups: [ + SettingsRadioGroup( + title: 'forward'.t(context: context), + value: SlideshowDirection.forward, + ), + SettingsRadioGroup( + title: 'backward'.t(context: context), + value: SlideshowDirection.backward, + ), + SettingsRadioGroup( + title: 'shuffle'.t(context: context), + value: SlideshowDirection.shuffle, + ), + ], + groupBy: useDirection.value, + onRadioChanged: (value) { + if (value != null) { + useDirection.value = value; + } + }, + ), + ), + ], + ); + } +} diff --git a/mobile/lib/widgets/settings/backup_settings/drift_backup_settings.dart b/mobile/lib/widgets/settings/backup_settings/drift_backup_settings.dart index 7ec4b21c1f..89d6f13f43 100644 --- a/mobile/lib/widgets/settings/backup_settings/drift_backup_settings.dart +++ b/mobile/lib/widgets/settings/backup_settings/drift_backup_settings.dart @@ -4,18 +4,17 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/models/config/app_config.dart'; +import 'package:immich_mobile/domain/models/metadata_key.dart'; import 'package:immich_mobile/domain/services/sync_linked_album.service.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/backup/backup_album.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/widgets/settings/setting_group_title.dart'; import 'package:immich_mobile/widgets/settings/setting_list_tile.dart'; import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart'; @@ -31,8 +30,8 @@ class DriftBackupSettings extends ConsumerWidget { title: "network_requirements".t(context: context), icon: Icons.cell_tower, ), - const _UseWifiForUploadVideosButton(), - const _UseWifiForUploadPhotosButton(), + const _UseCellularForVideosButton(), + const _UseCellularForPhotosButton(), if (CurrentPlatform.isAndroid) ...[ const Divider(), SettingGroupTitle( @@ -99,64 +98,58 @@ class _AlbumSyncActionButtonState extends ConsumerState<_AlbumSyncActionButton> @override Widget build(BuildContext context) { + final albumSyncEnable = ref.watch(appConfigProvider.select((c) => c.backup.syncAlbums)); return Padding( padding: const EdgeInsets.only(left: 8.0), child: ListView( shrinkWrap: true, children: [ - StreamBuilder( - stream: Store.watch(StoreKey.syncAlbums), - initialData: Store.tryGet(StoreKey.syncAlbums) ?? false, - builder: (context, snapshot) { - final albumSyncEnable = snapshot.data ?? false; - return Column( - children: [ - SettingListTile( - title: "sync_albums".t(context: context), - subtitle: "sync_upload_album_setting_subtitle".t(context: context), - trailing: Switch( - value: albumSyncEnable, - onChanged: (bool newValue) async { - await ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.syncAlbums, newValue); + Column( + children: [ + SettingListTile( + title: "sync_albums".t(context: context), + subtitle: "sync_upload_album_setting_subtitle".t(context: context), + trailing: Switch( + value: albumSyncEnable, + onChanged: (bool newValue) async { + await ref.read(metadataProvider).write(MetadataKey.backupSyncAlbums, newValue); - if (newValue == true) { - await _manageLinkedAlbums(); - } - }, - ), - ), - AnimatedSize( - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - child: AnimatedOpacity( - duration: const Duration(milliseconds: 200), - opacity: albumSyncEnable ? 1.0 : 0.0, - child: albumSyncEnable - ? SettingListTile( - onTap: _manualSyncAlbums, - contentPadding: const EdgeInsets.only(left: 32, right: 16), - title: "organize_into_albums".t(context: context), - subtitle: "organize_into_albums_description".t(context: context), - trailing: isAlbumSyncInProgress - ? const SizedBox( - width: 32, - height: 32, - child: CircularProgressIndicator.adaptive(strokeWidth: 2), - ) - : IconButton( - onPressed: _manualSyncAlbums, - icon: const Icon(Icons.sync_rounded), - color: context.colorScheme.onSurface.withValues(alpha: 0.7), - iconSize: 20, - constraints: const BoxConstraints(minWidth: 32, minHeight: 32), - ), - ) - : const SizedBox.shrink(), - ), - ), - ], - ); - }, + if (newValue == true) { + await _manageLinkedAlbums(); + } + }, + ), + ), + AnimatedSize( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + child: AnimatedOpacity( + duration: const Duration(milliseconds: 200), + opacity: albumSyncEnable ? 1.0 : 0.0, + child: albumSyncEnable + ? SettingListTile( + onTap: _manualSyncAlbums, + contentPadding: const EdgeInsets.only(left: 32, right: 16), + title: "organize_into_albums".t(context: context), + subtitle: "organize_into_albums_description".t(context: context), + trailing: isAlbumSyncInProgress + ? const SizedBox( + width: 32, + height: 32, + child: CircularProgressIndicator.adaptive(strokeWidth: 2), + ) + : IconButton( + onPressed: _manualSyncAlbums, + icon: const Icon(Icons.sync_rounded), + color: context.colorScheme.onSurface.withValues(alpha: 0.7), + iconSize: 20, + constraints: const BoxConstraints(minWidth: 32, minHeight: 32), + ), + ) + : const SizedBox.shrink(), + ), + ), + ], ), ], ), @@ -164,60 +157,34 @@ class _AlbumSyncActionButtonState extends ConsumerState<_AlbumSyncActionButton> } } -class _SettingsSwitchTile extends ConsumerStatefulWidget { - final AppSettingsEnum appSettingsEnum; +class _BackupSwitchTile extends ConsumerWidget { + final MetadataKey metadataKey; + final bool Function(AppConfig) selector; final String titleKey; final String subtitleKey; - final void Function(bool?)? onChanged; + final void Function(bool)? onChanged; - const _SettingsSwitchTile({ - required this.appSettingsEnum, + const _BackupSwitchTile({ + required this.metadataKey, + required this.selector, required this.titleKey, required this.subtitleKey, this.onChanged, }); @override - ConsumerState createState() => _SettingsSwitchTileState(); -} - -class _SettingsSwitchTileState extends ConsumerState<_SettingsSwitchTile> { - late final Stream valueStream; - late final StreamSubscription subscription; - - @override - void initState() { - super.initState(); - valueStream = Store.watch(widget.appSettingsEnum.storeKey).asBroadcastStream(); - subscription = valueStream.listen((value) { - widget.onChanged?.call(value); - }); - } - - @override - void dispose() { - subscription.cancel(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final value = ref.watch(appConfigProvider.select(selector)); return Padding( padding: const EdgeInsets.only(left: 8.0), child: SettingListTile( - title: widget.titleKey.t(context: context), - subtitle: widget.subtitleKey.t(context: context), - trailing: StreamBuilder( - stream: valueStream, - initialData: Store.tryGet(widget.appSettingsEnum.storeKey) ?? widget.appSettingsEnum.defaultValue, - builder: (context, snapshot) { - final value = snapshot.data ?? false; - return Switch( - value: value, - onChanged: (bool newValue) async { - await ref.read(appSettingsServiceProvider).setSetting(widget.appSettingsEnum, newValue); - }, - ); + title: titleKey.t(context: context), + subtitle: subtitleKey.t(context: context), + trailing: Switch( + value: value, + onChanged: (bool newValue) async { + await ref.read(metadataProvider).write(metadataKey, newValue); + onChanged?.call(newValue); }, ), ), @@ -225,26 +192,28 @@ class _SettingsSwitchTileState extends ConsumerState<_SettingsSwitchTile> { } } -class _UseWifiForUploadVideosButton extends ConsumerWidget { - const _UseWifiForUploadVideosButton(); +class _UseCellularForVideosButton extends StatelessWidget { + const _UseCellularForVideosButton(); @override - Widget build(BuildContext context, WidgetRef ref) { - return const _SettingsSwitchTile( - appSettingsEnum: AppSettingsEnum.useCellularForUploadVideos, + Widget build(BuildContext context) { + return _BackupSwitchTile( + metadataKey: MetadataKey.backupUseCellularForVideos, + selector: (c) => c.backup.useCellularForVideos, titleKey: "videos", subtitleKey: "network_requirement_videos_upload", ); } } -class _UseWifiForUploadPhotosButton extends ConsumerWidget { - const _UseWifiForUploadPhotosButton(); +class _UseCellularForPhotosButton extends StatelessWidget { + const _UseCellularForPhotosButton(); @override - Widget build(BuildContext context, WidgetRef ref) { - return const _SettingsSwitchTile( - appSettingsEnum: AppSettingsEnum.useCellularForUploadPhotos, + Widget build(BuildContext context) { + return _BackupSwitchTile( + metadataKey: MetadataKey.backupUseCellularForPhotos, + selector: (c) => c.backup.useCellularForPhotos, titleKey: "photos", subtitleKey: "network_requirement_photos_upload", ); @@ -256,29 +225,22 @@ class _BackupOnlyWhenChargingButton extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return _SettingsSwitchTile( - appSettingsEnum: AppSettingsEnum.backupRequireCharging, + final fgService = ref.read(backgroundWorkerFgServiceProvider); + return _BackupSwitchTile( + metadataKey: MetadataKey.backupRequireCharging, + selector: (c) => c.backup.requireCharging, titleKey: "charging", subtitleKey: "charging_requirement_mobile_backup", onChanged: (value) { - ref.read(backgroundWorkerFgServiceProvider).configure(requireCharging: value ?? false); + fgService.configure(requireCharging: value); }, ); } } -class _BackupDelaySlider extends ConsumerStatefulWidget { +class _BackupDelaySlider extends ConsumerWidget { const _BackupDelaySlider(); - @override - ConsumerState<_BackupDelaySlider> createState() => _BackupDelaySliderState(); -} - -class _BackupDelaySliderState extends ConsumerState<_BackupDelaySlider> { - late final Stream valueStream; - late final StreamSubscription subscription; - late int currentValue; - static int backupDelayToSliderValue(int ms) => switch (ms) { 5 => 0, 30 => 1, @@ -301,30 +263,9 @@ class _BackupDelaySliderState extends ConsumerState<_BackupDelaySlider> { }; @override - void initState() { - super.initState(); - final initialValue = - Store.tryGet(AppSettingsEnum.backupTriggerDelay.storeKey) ?? AppSettingsEnum.backupTriggerDelay.defaultValue; - currentValue = backupDelayToSliderValue(initialValue); - - valueStream = Store.watch(AppSettingsEnum.backupTriggerDelay.storeKey).asBroadcastStream(); - subscription = valueStream.listen((value) { - if (mounted && value != null) { - setState(() { - currentValue = backupDelayToSliderValue(value); - }); - } - }); - } - - @override - void dispose() { - subscription.cancel(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final triggerDelay = ref.watch(appConfigProvider.select((c) => c.backup.triggerDelay)); + final currentValue = backupDelayToSliderValue(triggerDelay); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -339,14 +280,13 @@ class _BackupDelaySliderState extends ConsumerState<_BackupDelaySlider> { ), Slider( value: currentValue.toDouble(), - onChanged: (double v) { - setState(() { - currentValue = v.toInt(); - }); + onChanged: (double v) async { + final seconds = backupDelayToSeconds(v.toInt()); + await ref.read(metadataProvider).write(MetadataKey.backupTriggerDelay, seconds); }, onChangeEnd: (double v) async { - final milliseconds = backupDelayToSeconds(v.toInt()); - await ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.backupTriggerDelay, milliseconds); + final seconds = backupDelayToSeconds(v.toInt()); + await ref.read(metadataProvider).write(MetadataKey.backupTriggerDelay, seconds); }, max: 3.0, min: 0.0, diff --git a/mobile/lib/widgets/settings/networking_settings/endpoint_input.dart b/mobile/lib/widgets/settings/networking_settings/endpoint_input.dart index 735971e0c2..e8310caed4 100644 --- a/mobile/lib/widgets/settings/networking_settings/endpoint_input.dart +++ b/mobile/lib/widgets/settings/networking_settings/endpoint_input.dart @@ -1,10 +1,10 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/widgets/settings/networking_settings/networking_settings.dart'; +import 'package:immich_ui/immich_ui.dart'; class EndpointInput extends StatefulHookConsumerWidget { const EndpointInput({ @@ -111,28 +111,12 @@ class EndpointInputState extends ConsumerState { status: auxCheckStatus, enabled: widget.enabled, ), - subtitle: TextFormField( + subtitle: ImmichURLInput( enabled: widget.enabled, - onTapOutside: (_) => focusNode.unfocus(), - autovalidateMode: AutovalidateMode.onUserInteraction, + autovalidateMode: .onUserInteraction, validator: validateUrl, - keyboardType: TextInputType.url, - style: const TextStyle(fontFamily: 'GoogleSansCode', fontSize: 14), - decoration: InputDecoration( - hintText: 'http(s)://immich.domain.com', - contentPadding: const EdgeInsets.all(16), - filled: true, - fillColor: context.colorScheme.surfaceContainer, - border: const OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(16))), - errorBorder: OutlineInputBorder( - borderSide: BorderSide(color: Colors.red[300]!), - borderRadius: const BorderRadius.all(Radius.circular(16)), - ), - disabledBorder: OutlineInputBorder( - borderSide: BorderSide(color: context.isDarkTheme ? Colors.grey[900]! : Colors.grey[300]!), - borderRadius: const BorderRadius.all(Radius.circular(16)), - ), - ), + keyboardAction: .next, + hintText: 'http(s)://immich.domain.com', controller: controller, focusNode: focusNode, ), diff --git a/mobile/lib/widgets/settings/networking_settings/external_network_preference.dart b/mobile/lib/widgets/settings/networking_settings/external_network_preference.dart index ba21acf49c..7900747055 100644 --- a/mobile/lib/widgets/settings/networking_settings/external_network_preference.dart +++ b/mobile/lib/widgets/settings/networking_settings/external_network_preference.dart @@ -1,13 +1,11 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart' hide Store; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/domain/models/metadata_key.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart'; +import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart'; import 'package:immich_mobile/widgets/settings/networking_settings/endpoint_input.dart'; class ExternalNetworkPreference extends HookConsumerWidget { @@ -23,11 +21,12 @@ class ExternalNetworkPreference extends HookConsumerWidget { saveEndpointList() { canSave.value = entries.value.every((e) => e.status == AuxCheckStatus.valid); - final endpointList = entries.value.where((url) => url.status == AuxCheckStatus.valid).toList(); + final urls = entries.value + .where((e) => e.status == AuxCheckStatus.valid && e.url.isNotEmpty) + .map((e) => e.url) + .toList(); - final jsonString = jsonEncode(endpointList); - - Store.put(StoreKey.externalEndpointList, jsonString); + ref.read(metadataProvider).write(MetadataKey.networkExternalEndpointList, urls); } updateValidationStatus(String url, int index, AuxCheckStatus status) { @@ -69,14 +68,13 @@ class ExternalNetworkPreference extends HookConsumerWidget { } useEffect(() { - final jsonString = Store.tryGet(StoreKey.externalEndpointList); + final urls = ref.read(metadataProvider).systemConfig.network.externalEndpointList; - if (jsonString == null) { + if (urls.isEmpty) { return null; } - final List jsonList = jsonDecode(jsonString); - entries.value = jsonList.map((e) => AuxilaryEndpoint.fromJson(e)).toList(); + entries.value = urls.map((url) => AuxilaryEndpoint(url: url, status: .valid)).toList(); return null; }, const []); diff --git a/mobile/lib/widgets/settings/networking_settings/local_network_preference.dart b/mobile/lib/widgets/settings/networking_settings/local_network_preference.dart index c89c8e149e..1d2b5eea0f 100644 --- a/mobile/lib/widgets/settings/networking_settings/local_network_preference.dart +++ b/mobile/lib/widgets/settings/networking_settings/local_network_preference.dart @@ -8,24 +8,29 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/network.provider.dart'; +import 'package:immich_ui/immich_ui.dart'; class LocalNetworkPreference extends HookConsumerWidget { const LocalNetworkPreference({super.key, required this.enabled}); final bool enabled; - Future _showEditDialog(BuildContext context, String title, String hintText, String initialValue) { + Future _showEditDialog( + BuildContext context, + String title, + String hintText, + String initialValue, { + bool isUrlField = false, + }) { final controller = TextEditingController(text: initialValue); return showDialog( context: context, builder: (context) => AlertDialog( title: Text(title), - content: TextField( - controller: controller, - autofocus: true, - decoration: InputDecoration(border: const OutlineInputBorder(), hintText: hintText), - ), + content: isUrlField + ? ImmichURLInput(controller: controller, autofocus: true, keyboardAction: .done, hintText: hintText) + : ImmichTextInput(controller: controller, autofocus: true, keyboardAction: .done, hintText: hintText), actions: [ TextButton( onPressed: () => Navigator.pop(context), @@ -81,6 +86,7 @@ class LocalNetworkPreference extends HookConsumerWidget { "server_endpoint".tr(), "http://local-ip:2283", localEndpointText.value, + isUrlField: true, ); if (localEndpoint != null) { diff --git a/mobile/lib/widgets/settings/networking_settings/networking_settings.dart b/mobile/lib/widgets/settings/networking_settings/networking_settings.dart index 981bec2c0c..f232f41a5d 100644 --- a/mobile/lib/widgets/settings/networking_settings/networking_settings.dart +++ b/mobile/lib/widgets/settings/networking_settings/networking_settings.dart @@ -1,13 +1,12 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart' hide Store; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart'; +import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart'; import 'package:immich_mobile/providers/network.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; -import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; import 'package:immich_mobile/utils/url_helper.dart'; import 'package:immich_mobile/widgets/settings/networking_settings/external_network_preference.dart'; import 'package:immich_mobile/widgets/settings/networking_settings/local_network_preference.dart'; @@ -20,7 +19,10 @@ class NetworkingSettings extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final currentEndpoint = getServerUrl(); - final featureEnabled = useAppSettingsState(AppSettingsEnum.autoEndpointSwitching); + final featureEnabled = useState(ref.read(systemConfigProvider).network.autoEndpointSwitching); + useValueChanged(featureEnabled.value, (_, __) { + ref.read(metadataProvider).write(.networkAutoEndpointSwitching, featureEnabled.value); + }); Future checkWifiReadPermission() async { final [hasLocationInUse, hasLocationAlways] = await Future.wait([ diff --git a/mobile/lib/widgets/shared_link/shared_link_item.dart b/mobile/lib/widgets/shared_link/shared_link_item.dart index 19da80b833..d419d6ead0 100644 --- a/mobile/lib/widgets/shared_link/shared_link_item.dart +++ b/mobile/lib/widgets/shared_link/shared_link_item.dart @@ -1,201 +1,140 @@ import 'dart:math' as math; import 'package:auto_route/auto_route.dart'; -import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/models/shared_link/shared_link.model.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/shared_link.provider.dart'; import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/utils/debug_print.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; import 'package:immich_mobile/utils/url_helper.dart'; import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; import 'package:immich_mobile/widgets/search/thumbnail_with_info.dart'; -import 'package:immich_mobile/utils/debug_print.dart'; class SharedLinkItem extends ConsumerWidget { final SharedLink sharedLink; const SharedLinkItem(this.sharedLink, {super.key}); - bool isExpired() { - if (sharedLink.expiresAt != null) { - return DateTime.now().isAfter(sharedLink.expiresAt!); - } - return false; - } + bool isExpired() => sharedLink.expiresAt?.isBefore(DateTime.now()) ?? false; + + Widget buildExpiryDuration(BuildContext context) { + var expiresText = context.t.shared_link_expires_never; + IconData expiryIcon = Icons.schedule; - Widget getExpiryDuration(bool isDarkMode) { - var expiresText = "shared_link_expires_never".tr(); if (sharedLink.expiresAt != null) { if (isExpired()) { - return Text("expired", style: TextStyle(color: Colors.red[300])).tr(); + expiresText = context.t.expired; + expiryIcon = Icons.timer_off_outlined; } + final difference = sharedLink.expiresAt!.difference(DateTime.now()); dPrint(() => "Difference: $difference"); + if (difference.inDays > 0) { var dayDifference = difference.inDays; if (difference.inHours % 24 > 12) { dayDifference += 1; } - expiresText = "shared_link_expires_days".tr(namedArgs: {'count': dayDifference.toString()}); + expiresText = context.t.shared_link_expires_days(count: dayDifference); } else if (difference.inHours > 0) { - expiresText = "shared_link_expires_hours".tr(namedArgs: {'count': difference.inHours.toString()}); + expiresText = context.t.shared_link_expires_hours(count: difference.inHours); } else if (difference.inMinutes > 0) { - expiresText = "shared_link_expires_minutes".tr(namedArgs: {'count': difference.inMinutes.toString()}); + expiresText = context.t.shared_link_expires_minutes(count: difference.inMinutes); } else if (difference.inSeconds > 0) { - expiresText = "shared_link_expires_seconds".tr(namedArgs: {'count': difference.inSeconds.toString()}); + expiresText = context.t.shared_link_expires_seconds(count: difference.inSeconds); } } - return Text(expiresText, style: TextStyle(color: isDarkMode ? Colors.grey[400] : Colors.grey[600])); + + return Row( + children: [ + Icon(expiryIcon, size: 12, color: isExpired() ? context.colorScheme.error : context.colorScheme.onSurface), + const SizedBox(width: 4), + Text( + expiresText, + style: TextStyle(color: isExpired() ? context.colorScheme.error : context.colorScheme.onSurface), + ), + ], + ); } @override Widget build(BuildContext context, WidgetRef ref) { - final colorScheme = context.colorScheme; - final isDarkMode = colorScheme.brightness == Brightness.dark; final thumbnailUrl = sharedLink.thumbAssetId != null ? getThumbnailUrlForRemoteId(sharedLink.thumbAssetId!) : null; final imageSize = math.min(context.width / 4, 100.0); - void copyShareLinkToClipboard() { + Future copyShareLinkToClipboard() async { final externalDomain = ref.read(serverInfoProvider.select((s) => s.serverConfig.externalDomain)); - var serverUrl = externalDomain.isNotEmpty ? externalDomain : getServerUrl(); - if (serverUrl != null && !serverUrl.endsWith('/')) { - serverUrl += '/'; - } - if (serverUrl == null) { + final serverUrl = externalDomain.isNotEmpty ? externalDomain : getServerUrl(); + final shareUrl = buildSharedLinkUrl(baseUrl: serverUrl, slug: sharedLink.slug, key: sharedLink.key); + + if (shareUrl == null) { ImmichToast.show( context: context, gravity: ToastGravity.BOTTOM, toastType: ToastType.error, - msg: "shared_link_error_server_url_fetch".tr(), + msg: context.t.shared_link_error_server_url_fetch, ); return; } - final hasSlug = sharedLink.slug?.isNotEmpty == true; - final urlPath = hasSlug ? sharedLink.slug : sharedLink.key; - final basePath = hasSlug ? 's' : 'share'; - Clipboard.setData(ClipboardData(text: "$serverUrl$basePath/$urlPath")).then((_) { - context.scaffoldMessenger.showSnackBar( - SnackBar( - content: Text( - "shared_link_clipboard_copied_massage", - style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor), - ).tr(), - duration: const Duration(seconds: 2), + await Clipboard.setData(ClipboardData(text: shareUrl)); + if (!context.mounted) { + return; + } + context.scaffoldMessenger.showSnackBar( + SnackBar( + content: Text( + context.t.shared_link_clipboard_copied_massage, + style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor), ), - ); - }); - } - - Future deleteShareLink() async { - return showDialog( - context: context, - builder: (BuildContext context) { - return ConfirmDialog( - title: "delete_shared_link_dialog_title", - content: "confirm_delete_shared_link", - onOk: () => ref.read(sharedLinksStateProvider.notifier).deleteLink(sharedLink.id), - ); - }, + duration: const Duration(seconds: 2), + ), ); } Widget buildThumbnail() { - if (thumbnailUrl == null) { - return Container( - height: imageSize * 1.2, - width: imageSize, - decoration: BoxDecoration(color: isDarkMode ? Colors.grey[800] : Colors.grey[200]), - child: Center( - child: Icon(Icons.image_not_supported_outlined, color: isDarkMode ? Colors.grey[100] : Colors.grey[700]), - ), - ); - } return SizedBox( height: imageSize * 1.2, width: imageSize, - child: Padding( - padding: const EdgeInsets.only(right: 4.0), - child: ThumbnailWithInfo( - imageUrl: thumbnailUrl, - key: key, - textInfo: '', - noImageIcon: Icons.image_not_supported_outlined, - onTap: () {}, - ), - ), + child: thumbnailUrl == null + ? const Card( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10))), + child: Icon(Icons.image_not_supported_outlined), + ) + : ThumbnailWithInfo( + imageUrl: thumbnailUrl, + key: key, + textInfo: '', + noImageIcon: Icons.image_not_supported_outlined, + onTap: () => context.pushRoute(SharedLinkEditRoute(existingLink: sharedLink)), + ), ); } Widget buildInfoChip(String labelText) { - return Padding( - padding: const EdgeInsets.only(right: 10), - child: Chip( - backgroundColor: colorScheme.primary, - label: Text( - labelText, - style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.w500, - color: isDarkMode ? Colors.black : Colors.white, - ), - ), - shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(25))), + return Card.outlined( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), + child: Text(labelText, style: const TextStyle(fontSize: 11)), ), ); } - Widget buildBottomInfo() { + Widget buildShareParameterInfos() { return Row( + spacing: 4, children: [ - if (sharedLink.allowUpload) buildInfoChip("upload".tr()), - if (sharedLink.allowDownload) buildInfoChip("download".tr()), - if (sharedLink.showMetadata) buildInfoChip("shared_link_info_chip_metadata".tr()), - ], - ); - } - - Widget buildSharedLinkActions() { - const actionIconSize = 20.0; - return Row( - children: [ - IconButton( - splashRadius: 25, - constraints: const BoxConstraints(), - iconSize: actionIconSize, - icon: const Icon(Icons.delete_outline), - style: const ButtonStyle( - tapTargetSize: MaterialTapTargetSize.shrinkWrap, // the '2023' part - ), - onPressed: deleteShareLink, - ), - IconButton( - splashRadius: 25, - constraints: const BoxConstraints(), - iconSize: actionIconSize, - icon: const Icon(Icons.edit_outlined), - style: const ButtonStyle( - tapTargetSize: MaterialTapTargetSize.shrinkWrap, // the '2023' part - ), - onPressed: () => context.pushRoute(SharedLinkEditRoute(existingLink: sharedLink)), - ), - IconButton( - splashRadius: 25, - constraints: const BoxConstraints(), - iconSize: actionIconSize, - icon: const Icon(Icons.copy_outlined), - style: const ButtonStyle( - tapTargetSize: MaterialTapTargetSize.shrinkWrap, // the '2023' part - ), - onPressed: copyShareLinkToClipboard, - ), + if (sharedLink.allowUpload) buildInfoChip(context.t.upload), + if (sharedLink.allowDownload) buildInfoChip(context.t.download), + if (sharedLink.showMetadata) buildInfoChip(context.t.shared_link_info_chip_metadata), ], ); } @@ -204,69 +143,64 @@ class SharedLinkItem extends ConsumerWidget { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - getExpiryDuration(isDarkMode), - Padding( - padding: const EdgeInsets.only(top: 5), - child: Tooltip( - verticalOffset: 0, - decoration: BoxDecoration( - color: colorScheme.primary.withValues(alpha: 0.9), - borderRadius: const BorderRadius.all(Radius.circular(10)), - ), - textStyle: TextStyle(color: isDarkMode ? Colors.black : Colors.white, fontWeight: FontWeight.bold), - message: sharedLink.title, - preferBelow: false, - triggerMode: TooltipTriggerMode.tap, - child: Text( - sharedLink.title, - style: TextStyle( - color: colorScheme.primary, - fontWeight: FontWeight.bold, - overflow: TextOverflow.ellipsis, - ), - ), + const SizedBox(height: 5), + Text( + sharedLink.title, + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.bold, + overflow: TextOverflow.ellipsis, ), ), - Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Tooltip( - verticalOffset: 0, - decoration: BoxDecoration( - color: colorScheme.primary.withValues(alpha: 0.9), - borderRadius: const BorderRadius.all(Radius.circular(10)), - ), - textStyle: TextStyle(color: isDarkMode ? Colors.black : Colors.white, fontWeight: FontWeight.bold), - message: sharedLink.description ?? "", - preferBelow: false, - triggerMode: TooltipTriggerMode.tap, - child: Text(sharedLink.description ?? "", overflow: TextOverflow.ellipsis), - ), - ), - Padding(padding: const EdgeInsets.only(right: 15), child: buildSharedLinkActions()), - ], - ), - buildBottomInfo(), + if (sharedLink.description?.isNotEmpty ?? false) + Text(sharedLink.description!, overflow: TextOverflow.ellipsis), + buildExpiryDuration(context), + buildShareParameterInfos(), ], ); } - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding(padding: const EdgeInsets.only(left: 15), child: buildThumbnail()), - Expanded( - child: Padding(padding: const EdgeInsets.only(left: 15), child: buildSharedLinkDetails()), - ), - ], + return Dismissible( + key: ValueKey(sharedLink.id), + direction: DismissDirection.endToStart, + background: Container( + color: Theme.of(context).colorScheme.error, + alignment: Alignment.centerRight, + padding: const EdgeInsets.only(right: 20), + child: Icon(Icons.delete, color: Theme.of(context).colorScheme.onError), + ), + confirmDismiss: (_) async { + final confirmed = await showDialog( + context: context, + builder: (BuildContext context) => ConfirmDialog( + title: "delete_shared_link_dialog_title", + content: "confirm_delete_shared_link", + onOk: () {}, + ), + ); + + if (confirmed == true) { + await ref.read(sharedLinksStateProvider.notifier).deleteLink(sharedLink.id); + return true; + } + + return false; + }, + child: InkWell( + onTap: () => context.pushRoute(SharedLinkEditRoute(existingLink: sharedLink)), + onLongPress: copyShareLinkToClipboard, + child: Padding( + padding: const EdgeInsets.all(12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildThumbnail(), + const SizedBox(width: 12), + Expanded(child: buildSharedLinkDetails()), + ], + ), ), - const Padding(padding: EdgeInsets.all(20), child: Divider(height: 0)), - ], + ), ); } } diff --git a/mobile/makefile b/mobile/makefile index 5a21287b85..645316efee 100644 --- a/mobile/makefile +++ b/mobile/makefile @@ -1,26 +1,26 @@ .PHONY: build watch create_app_icon create_splash build_release_android pigeon test analyze format migration translation build: - @printf "This command has been removed. Please use:\n\n mise codegen # or mise //:mobile:codegen:dart from another directory\n\n" >&2 && exit 1 + @printf "This command has been removed. Please use:\n\n mise codegen # or mise //mobile:codegen:dart from another directory\n\n" >&2 && exit 1 pigeon: - @printf "This command has been removed. Please use:\n\n mise pigeon # or mise //:mobile:codegen:pigeon from another directory\n\n" >&2 && exit 1 + @printf "This command has been removed. Please use:\n\n mise pigeon # or mise //mobile:codegen:pigeon from another directory\n\n" >&2 && exit 1 build_release_android: - @printf "This command has been removed. Please use:\n\n mise run build:android # or mise //:mobile:build:android from another directory\n\n" >&2 && exit 1 + @printf "This command has been removed. Please use:\n\n mise run build:android # or mise //mobile:build:android from another directory\n\n" >&2 && exit 1 migration: - @printf "This command has been removed. Please use:\n\n mise migration # or mise //:mobile:drift:migration from another directory\n\n" >&2 && exit 1 + @printf "This command has been removed. Please use:\n\n mise migration # or mise //mobile:drift:migration from another directory\n\n" >&2 && exit 1 translation: - @printf "This command has been removed. Please use:\n\n mise translation # or mise //:mobile:codegen:translation from another directory\n\n" >&2 && exit 1 + @printf "This command has been removed. Please use:\n\n mise translation # or mise //mobile:codegen:translation from another directory\n\n" >&2 && exit 1 analyze: - @printf "This command has been removed. Please use:\n\n mise analyze # or mise //:mobile:lint from another directory\n\n" >&2 && exit 1 + @printf "This command has been removed. Please use:\n\n mise analyze # or mise //mobile:lint from another directory\n\n" >&2 && exit 1 format: - @printf "This command has been removed. Please use:\n\n mise format # or mise //:mobile:format from another directory\n\n" >&2 && exit 1 + @printf "This command has been removed. Please use:\n\n mise format # or mise //mobile:format from another directory\n\n" >&2 && exit 1 test: - @printf "This command has been removed. Please use:\n\n mise test # or mise //:mobile:test from another directory\n\n" >&2 && exit 1 + @printf "This command has been removed. Please use:\n\n mise test # or mise //mobile:test from another directory\n\n" >&2 && exit 1 diff --git a/mobile/mise.toml b/mobile/mise.toml index c6d09695d4..01177dca37 100644 --- a/mobile/mise.toml +++ b/mobile/mise.toml @@ -1,11 +1,3 @@ -[tools] -flutter = "3.41.9" - -[tools."github:CQLabs/homebrew-dcm"] -version = "1.30.0" -bin = "dcm" -postinstall = "chmod +x $MISE_TOOL_INSTALL_PATH/dcm" - [tasks."codegen:dart"] alias = "codegen" description = "Execute build_runner to auto-generate dart code" diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 2538f8e7a7..ecc75dd945 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -205,8 +205,8 @@ Class | Method | HTTP request | Description *PeopleApi* | [**updatePeople**](doc//PeopleApi.md#updatepeople) | **PUT** /people | Update people *PeopleApi* | [**updatePerson**](doc//PeopleApi.md#updateperson) | **PUT** /people/{id} | Update person *PluginsApi* | [**getPlugin**](doc//PluginsApi.md#getplugin) | **GET** /plugins/{id} | Retrieve a plugin -*PluginsApi* | [**getPluginTriggers**](doc//PluginsApi.md#getplugintriggers) | **GET** /plugins/triggers | List all plugin triggers -*PluginsApi* | [**getPlugins**](doc//PluginsApi.md#getplugins) | **GET** /plugins | List all plugins +*PluginsApi* | [**searchPluginMethods**](doc//PluginsApi.md#searchpluginmethods) | **GET** /plugins/methods | Retrieve plugin methods +*PluginsApi* | [**searchPlugins**](doc//PluginsApi.md#searchplugins) | **GET** /plugins | List all plugins *QueuesApi* | [**emptyQueue**](doc//QueuesApi.md#emptyqueue) | **DELETE** /queues/{name}/jobs | Empty a queue *QueuesApi* | [**getQueue**](doc//QueuesApi.md#getqueue) | **GET** /queues/{name} | Retrieve a queue *QueuesApi* | [**getQueueJobs**](doc//QueuesApi.md#getqueuejobs) | **GET** /queues/{name}/jobs | Retrieve queue jobs @@ -314,7 +314,9 @@ Class | Method | HTTP request | Description *WorkflowsApi* | [**createWorkflow**](doc//WorkflowsApi.md#createworkflow) | **POST** /workflows | Create a workflow *WorkflowsApi* | [**deleteWorkflow**](doc//WorkflowsApi.md#deleteworkflow) | **DELETE** /workflows/{id} | Delete a workflow *WorkflowsApi* | [**getWorkflow**](doc//WorkflowsApi.md#getworkflow) | **GET** /workflows/{id} | Retrieve a workflow -*WorkflowsApi* | [**getWorkflows**](doc//WorkflowsApi.md#getworkflows) | **GET** /workflows | List all workflows +*WorkflowsApi* | [**getWorkflowForShare**](doc//WorkflowsApi.md#getworkflowforshare) | **GET** /workflows/{id}/share | Retrieve a workflow +*WorkflowsApi* | [**getWorkflowTriggers**](doc//WorkflowsApi.md#getworkflowtriggers) | **GET** /workflows/triggers | List all workflow triggers +*WorkflowsApi* | [**searchWorkflows**](doc//WorkflowsApi.md#searchworkflows) | **GET** /workflows | List all workflows *WorkflowsApi* | [**updateWorkflow**](doc//WorkflowsApi.md#updateworkflow) | **PUT** /workflows/{id} | Update a workflow @@ -487,16 +489,8 @@ Class | Method | HTTP request | Description - [PinCodeResetDto](doc//PinCodeResetDto.md) - [PinCodeSetupDto](doc//PinCodeSetupDto.md) - [PlacesResponseDto](doc//PlacesResponseDto.md) - - [PluginActionResponseDto](doc//PluginActionResponseDto.md) - - [PluginContextType](doc//PluginContextType.md) - - [PluginFilterResponseDto](doc//PluginFilterResponseDto.md) - - [PluginJsonSchema](doc//PluginJsonSchema.md) - - [PluginJsonSchemaProperty](doc//PluginJsonSchemaProperty.md) - - [PluginJsonSchemaPropertyAdditionalProperties](doc//PluginJsonSchemaPropertyAdditionalProperties.md) - - [PluginJsonSchemaType](doc//PluginJsonSchemaType.md) + - [PluginMethodResponseDto](doc//PluginMethodResponseDto.md) - [PluginResponseDto](doc//PluginResponseDto.md) - - [PluginTriggerResponseDto](doc//PluginTriggerResponseDto.md) - - [PluginTriggerType](doc//PluginTriggerType.md) - [PurchaseResponse](doc//PurchaseResponse.md) - [PurchaseUpdate](doc//PurchaseUpdate.md) - [QueueCommand](doc//QueueCommand.md) @@ -669,12 +663,14 @@ Class | Method | HTTP request | Description - [VersionCheckStateResponseDto](doc//VersionCheckStateResponseDto.md) - [VideoCodec](doc//VideoCodec.md) - [VideoContainer](doc//VideoContainer.md) - - [WorkflowActionItemDto](doc//WorkflowActionItemDto.md) - - [WorkflowActionResponseDto](doc//WorkflowActionResponseDto.md) - [WorkflowCreateDto](doc//WorkflowCreateDto.md) - - [WorkflowFilterItemDto](doc//WorkflowFilterItemDto.md) - - [WorkflowFilterResponseDto](doc//WorkflowFilterResponseDto.md) - [WorkflowResponseDto](doc//WorkflowResponseDto.md) + - [WorkflowShareResponseDto](doc//WorkflowShareResponseDto.md) + - [WorkflowShareStepDto](doc//WorkflowShareStepDto.md) + - [WorkflowStepDto](doc//WorkflowStepDto.md) + - [WorkflowTrigger](doc//WorkflowTrigger.md) + - [WorkflowTriggerResponseDto](doc//WorkflowTriggerResponseDto.md) + - [WorkflowType](doc//WorkflowType.md) - [WorkflowUpdateDto](doc//WorkflowUpdateDto.md) diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 097f0b41bb..1769c8af75 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -235,16 +235,8 @@ part 'model/pin_code_change_dto.dart'; part 'model/pin_code_reset_dto.dart'; part 'model/pin_code_setup_dto.dart'; part 'model/places_response_dto.dart'; -part 'model/plugin_action_response_dto.dart'; -part 'model/plugin_context_type.dart'; -part 'model/plugin_filter_response_dto.dart'; -part 'model/plugin_json_schema.dart'; -part 'model/plugin_json_schema_property.dart'; -part 'model/plugin_json_schema_property_additional_properties.dart'; -part 'model/plugin_json_schema_type.dart'; +part 'model/plugin_method_response_dto.dart'; part 'model/plugin_response_dto.dart'; -part 'model/plugin_trigger_response_dto.dart'; -part 'model/plugin_trigger_type.dart'; part 'model/purchase_response.dart'; part 'model/purchase_update.dart'; part 'model/queue_command.dart'; @@ -417,12 +409,14 @@ part 'model/validate_library_response_dto.dart'; part 'model/version_check_state_response_dto.dart'; part 'model/video_codec.dart'; part 'model/video_container.dart'; -part 'model/workflow_action_item_dto.dart'; -part 'model/workflow_action_response_dto.dart'; part 'model/workflow_create_dto.dart'; -part 'model/workflow_filter_item_dto.dart'; -part 'model/workflow_filter_response_dto.dart'; part 'model/workflow_response_dto.dart'; +part 'model/workflow_share_response_dto.dart'; +part 'model/workflow_share_step_dto.dart'; +part 'model/workflow_step_dto.dart'; +part 'model/workflow_trigger.dart'; +part 'model/workflow_trigger_response_dto.dart'; +part 'model/workflow_type.dart'; part 'model/workflow_update_dto.dart'; diff --git a/mobile/openapi/lib/api/plugins_api.dart b/mobile/openapi/lib/api/plugins_api.dart index 5735fba379..d2a1d386d9 100644 --- a/mobile/openapi/lib/api/plugins_api.dart +++ b/mobile/openapi/lib/api/plugins_api.dart @@ -73,14 +73,40 @@ class PluginsApi { return null; } - /// List all plugin triggers + /// Retrieve plugin methods /// - /// Retrieve a list of all available plugin triggers. + /// Retrieve a list of plugin methods /// /// Note: This method returns the HTTP [Response]. - Future getPluginTriggersWithHttpInfo() async { + /// + /// Parameters: + /// + /// * [String] description: + /// + /// * [bool] enabled: + /// Whether the plugin method is enabled + /// + /// * [String] id: + /// Plugin method ID + /// + /// * [String] name: + /// + /// * [String] pluginName: + /// Plugin name + /// + /// * [String] pluginVersion: + /// Plugin version + /// + /// * [String] title: + /// + /// * [WorkflowTrigger] trigger: + /// Workflow trigger + /// + /// * [WorkflowType] type: + /// Workflow types + Future searchPluginMethodsWithHttpInfo({ String? description, bool? enabled, String? id, String? name, String? pluginName, String? pluginVersion, String? title, WorkflowTrigger? trigger, WorkflowType? type, }) async { // ignore: prefer_const_declarations - final apiPath = r'/plugins/triggers'; + final apiPath = r'/plugins/methods'; // ignore: prefer_final_locals Object? postBody; @@ -89,6 +115,34 @@ class PluginsApi { final headerParams = {}; final formParams = {}; + if (description != null) { + queryParams.addAll(_queryParams('', 'description', description)); + } + if (enabled != null) { + queryParams.addAll(_queryParams('', 'enabled', enabled)); + } + if (id != null) { + queryParams.addAll(_queryParams('', 'id', id)); + } + if (name != null) { + queryParams.addAll(_queryParams('', 'name', name)); + } + if (pluginName != null) { + queryParams.addAll(_queryParams('', 'pluginName', pluginName)); + } + if (pluginVersion != null) { + queryParams.addAll(_queryParams('', 'pluginVersion', pluginVersion)); + } + if (title != null) { + queryParams.addAll(_queryParams('', 'title', title)); + } + if (trigger != null) { + queryParams.addAll(_queryParams('', 'trigger', trigger)); + } + if (type != null) { + queryParams.addAll(_queryParams('', 'type', type)); + } + const contentTypes = []; @@ -103,11 +157,37 @@ class PluginsApi { ); } - /// List all plugin triggers + /// Retrieve plugin methods /// - /// Retrieve a list of all available plugin triggers. - Future?> getPluginTriggers() async { - final response = await getPluginTriggersWithHttpInfo(); + /// Retrieve a list of plugin methods + /// + /// Parameters: + /// + /// * [String] description: + /// + /// * [bool] enabled: + /// Whether the plugin method is enabled + /// + /// * [String] id: + /// Plugin method ID + /// + /// * [String] name: + /// + /// * [String] pluginName: + /// Plugin name + /// + /// * [String] pluginVersion: + /// Plugin version + /// + /// * [String] title: + /// + /// * [WorkflowTrigger] trigger: + /// Workflow trigger + /// + /// * [WorkflowType] type: + /// Workflow types + Future?> searchPluginMethods({ String? description, bool? enabled, String? id, String? name, String? pluginName, String? pluginVersion, String? title, WorkflowTrigger? trigger, WorkflowType? type, }) async { + final response = await searchPluginMethodsWithHttpInfo( description: description, enabled: enabled, id: id, name: name, pluginName: pluginName, pluginVersion: pluginVersion, title: title, trigger: trigger, type: type, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -116,8 +196,8 @@ class PluginsApi { // FormatException when trying to decode an empty string. if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { final responseBody = await _decodeBodyBytes(response); - return (await apiClient.deserializeAsync(responseBody, 'List') as List) - .cast() + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() .toList(growable: false); } @@ -129,7 +209,23 @@ class PluginsApi { /// Retrieve a list of plugins available to the authenticated user. /// /// Note: This method returns the HTTP [Response]. - Future getPluginsWithHttpInfo() async { + /// + /// Parameters: + /// + /// * [String] description: + /// + /// * [bool] enabled: + /// Whether the plugin is enabled + /// + /// * [String] id: + /// Plugin ID + /// + /// * [String] name: + /// + /// * [String] title: + /// + /// * [String] version: + Future searchPluginsWithHttpInfo({ String? description, bool? enabled, String? id, String? name, String? title, String? version, }) async { // ignore: prefer_const_declarations final apiPath = r'/plugins'; @@ -140,6 +236,25 @@ class PluginsApi { final headerParams = {}; final formParams = {}; + if (description != null) { + queryParams.addAll(_queryParams('', 'description', description)); + } + if (enabled != null) { + queryParams.addAll(_queryParams('', 'enabled', enabled)); + } + if (id != null) { + queryParams.addAll(_queryParams('', 'id', id)); + } + if (name != null) { + queryParams.addAll(_queryParams('', 'name', name)); + } + if (title != null) { + queryParams.addAll(_queryParams('', 'title', title)); + } + if (version != null) { + queryParams.addAll(_queryParams('', 'version', version)); + } + const contentTypes = []; @@ -157,8 +272,24 @@ class PluginsApi { /// List all plugins /// /// Retrieve a list of plugins available to the authenticated user. - Future?> getPlugins() async { - final response = await getPluginsWithHttpInfo(); + /// + /// Parameters: + /// + /// * [String] description: + /// + /// * [bool] enabled: + /// Whether the plugin is enabled + /// + /// * [String] id: + /// Plugin ID + /// + /// * [String] name: + /// + /// * [String] title: + /// + /// * [String] version: + Future?> searchPlugins({ String? description, bool? enabled, String? id, String? name, String? title, String? version, }) async { + final response = await searchPluginsWithHttpInfo( description: description, enabled: enabled, id: id, name: name, title: title, version: version, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/workflows_api.dart b/mobile/openapi/lib/api/workflows_api.dart index c589ec9823..12b33b7238 100644 --- a/mobile/openapi/lib/api/workflows_api.dart +++ b/mobile/openapi/lib/api/workflows_api.dart @@ -178,14 +178,19 @@ class WorkflowsApi { return null; } - /// List all workflows + /// Retrieve a workflow /// - /// Retrieve a list of workflows available to the authenticated user. + /// Retrieve a workflow details without ids, default values, etc. /// /// Note: This method returns the HTTP [Response]. - Future getWorkflowsWithHttpInfo() async { + /// + /// Parameters: + /// + /// * [String] id (required): + Future getWorkflowForShareWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final apiPath = r'/workflows'; + final apiPath = r'/workflows/{id}/share' + .replaceAll('{id}', id); // ignore: prefer_final_locals Object? postBody; @@ -208,11 +213,164 @@ class WorkflowsApi { ); } + /// Retrieve a workflow + /// + /// Retrieve a workflow details without ids, default values, etc. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getWorkflowForShare(String id,) async { + final response = await getWorkflowForShareWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'WorkflowShareResponseDto',) as WorkflowShareResponseDto; + + } + return null; + } + + /// List all workflow triggers + /// + /// Retrieve a list of all available workflow triggers. + /// + /// Note: This method returns the HTTP [Response]. + Future getWorkflowTriggersWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/workflows/triggers'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// List all workflow triggers + /// + /// Retrieve a list of all available workflow triggers. + Future?> getWorkflowTriggers() async { + final response = await getWorkflowTriggersWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + /// List all workflows /// /// Retrieve a list of workflows available to the authenticated user. - Future?> getWorkflows() async { - final response = await getWorkflowsWithHttpInfo(); + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] description: + /// Workflow description + /// + /// * [bool] enabled: + /// Workflow enabled + /// + /// * [String] id: + /// Workflow ID + /// + /// * [String] name: + /// Workflow name + /// + /// * [WorkflowTrigger] trigger: + /// Workflow trigger type + Future searchWorkflowsWithHttpInfo({ String? description, bool? enabled, String? id, String? name, WorkflowTrigger? trigger, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/workflows'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (description != null) { + queryParams.addAll(_queryParams('', 'description', description)); + } + if (enabled != null) { + queryParams.addAll(_queryParams('', 'enabled', enabled)); + } + if (id != null) { + queryParams.addAll(_queryParams('', 'id', id)); + } + if (name != null) { + queryParams.addAll(_queryParams('', 'name', name)); + } + if (trigger != null) { + queryParams.addAll(_queryParams('', 'trigger', trigger)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// List all workflows + /// + /// Retrieve a list of workflows available to the authenticated user. + /// + /// Parameters: + /// + /// * [String] description: + /// Workflow description + /// + /// * [bool] enabled: + /// Workflow enabled + /// + /// * [String] id: + /// Workflow ID + /// + /// * [String] name: + /// Workflow name + /// + /// * [WorkflowTrigger] trigger: + /// Workflow trigger type + Future?> searchWorkflows({ String? description, bool? enabled, String? id, String? name, WorkflowTrigger? trigger, }) async { + final response = await searchWorkflowsWithHttpInfo( description: description, enabled: enabled, id: id, name: name, trigger: trigger, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index e04f800d3e..103a5db5f4 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -516,26 +516,10 @@ class ApiClient { return PinCodeSetupDto.fromJson(value); case 'PlacesResponseDto': return PlacesResponseDto.fromJson(value); - case 'PluginActionResponseDto': - return PluginActionResponseDto.fromJson(value); - case 'PluginContextType': - return PluginContextTypeTypeTransformer().decode(value); - case 'PluginFilterResponseDto': - return PluginFilterResponseDto.fromJson(value); - case 'PluginJsonSchema': - return PluginJsonSchema.fromJson(value); - case 'PluginJsonSchemaProperty': - return PluginJsonSchemaProperty.fromJson(value); - case 'PluginJsonSchemaPropertyAdditionalProperties': - return PluginJsonSchemaPropertyAdditionalProperties.fromJson(value); - case 'PluginJsonSchemaType': - return PluginJsonSchemaTypeTypeTransformer().decode(value); + case 'PluginMethodResponseDto': + return PluginMethodResponseDto.fromJson(value); case 'PluginResponseDto': return PluginResponseDto.fromJson(value); - case 'PluginTriggerResponseDto': - return PluginTriggerResponseDto.fromJson(value); - case 'PluginTriggerType': - return PluginTriggerTypeTypeTransformer().decode(value); case 'PurchaseResponse': return PurchaseResponse.fromJson(value); case 'PurchaseUpdate': @@ -880,18 +864,22 @@ class ApiClient { return VideoCodecTypeTransformer().decode(value); case 'VideoContainer': return VideoContainerTypeTransformer().decode(value); - case 'WorkflowActionItemDto': - return WorkflowActionItemDto.fromJson(value); - case 'WorkflowActionResponseDto': - return WorkflowActionResponseDto.fromJson(value); case 'WorkflowCreateDto': return WorkflowCreateDto.fromJson(value); - case 'WorkflowFilterItemDto': - return WorkflowFilterItemDto.fromJson(value); - case 'WorkflowFilterResponseDto': - return WorkflowFilterResponseDto.fromJson(value); case 'WorkflowResponseDto': return WorkflowResponseDto.fromJson(value); + case 'WorkflowShareResponseDto': + return WorkflowShareResponseDto.fromJson(value); + case 'WorkflowShareStepDto': + return WorkflowShareStepDto.fromJson(value); + case 'WorkflowStepDto': + return WorkflowStepDto.fromJson(value); + case 'WorkflowTrigger': + return WorkflowTriggerTypeTransformer().decode(value); + case 'WorkflowTriggerResponseDto': + return WorkflowTriggerResponseDto.fromJson(value); + case 'WorkflowType': + return WorkflowTypeTypeTransformer().decode(value); case 'WorkflowUpdateDto': return WorkflowUpdateDto.fromJson(value); default: diff --git a/mobile/openapi/lib/api_helper.dart b/mobile/openapi/lib/api_helper.dart index 340962cde5..b5d348edd6 100644 --- a/mobile/openapi/lib/api_helper.dart +++ b/mobile/openapi/lib/api_helper.dart @@ -142,15 +142,6 @@ String parameterToString(dynamic value) { if (value is Permission) { return PermissionTypeTransformer().encode(value).toString(); } - if (value is PluginContextType) { - return PluginContextTypeTypeTransformer().encode(value).toString(); - } - if (value is PluginJsonSchemaType) { - return PluginJsonSchemaTypeTypeTransformer().encode(value).toString(); - } - if (value is PluginTriggerType) { - return PluginTriggerTypeTypeTransformer().encode(value).toString(); - } if (value is QueueCommand) { return QueueCommandTypeTransformer().encode(value).toString(); } @@ -208,6 +199,12 @@ String parameterToString(dynamic value) { if (value is VideoContainer) { return VideoContainerTypeTransformer().encode(value).toString(); } + if (value is WorkflowTrigger) { + return WorkflowTriggerTypeTransformer().encode(value).toString(); + } + if (value is WorkflowType) { + return WorkflowTypeTypeTransformer().encode(value).toString(); + } return value.toString(); } diff --git a/mobile/openapi/lib/model/job_name.dart b/mobile/openapi/lib/model/job_name.dart index 08f70569f8..444b080c12 100644 --- a/mobile/openapi/lib/model/job_name.dart +++ b/mobile/openapi/lib/model/job_name.dart @@ -77,7 +77,7 @@ class JobName { static const versionCheck = JobName._(r'VersionCheck'); static const ocrQueueAll = JobName._(r'OcrQueueAll'); static const ocr = JobName._(r'Ocr'); - static const workflowRun = JobName._(r'WorkflowRun'); + static const workflowAssetCreate = JobName._(r'WorkflowAssetCreate'); /// List of all possible values in this [enum][JobName]. static const values = [ @@ -135,7 +135,7 @@ class JobName { versionCheck, ocrQueueAll, ocr, - workflowRun, + workflowAssetCreate, ]; static JobName? fromJson(dynamic value) => JobNameTypeTransformer().decode(value); @@ -228,7 +228,7 @@ class JobNameTypeTransformer { case r'VersionCheck': return JobName.versionCheck; case r'OcrQueueAll': return JobName.ocrQueueAll; case r'Ocr': return JobName.ocr; - case r'WorkflowRun': return JobName.workflowRun; + case r'WorkflowAssetCreate': return JobName.workflowAssetCreate; default: if (!allowNull) { throw ArgumentError('Unknown enum value to decode: $data'); diff --git a/mobile/openapi/lib/model/plugin_action_response_dto.dart b/mobile/openapi/lib/model/plugin_action_response_dto.dart deleted file mode 100644 index cff2dc92f7..0000000000 --- a/mobile/openapi/lib/model/plugin_action_response_dto.dart +++ /dev/null @@ -1,158 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - -class PluginActionResponseDto { - /// Returns a new [PluginActionResponseDto] instance. - PluginActionResponseDto({ - required this.description, - required this.id, - required this.methodName, - required this.pluginId, - required this.schema, - this.supportedContexts = const [], - required this.title, - }); - - /// Action description - String description; - - /// Action ID - String id; - - /// Method name - String methodName; - - /// Plugin ID - String pluginId; - - /// Action schema - PluginJsonSchema? schema; - - /// Supported contexts - List supportedContexts; - - /// Action title - String title; - - @override - bool operator ==(Object other) => identical(this, other) || other is PluginActionResponseDto && - other.description == description && - other.id == id && - other.methodName == methodName && - other.pluginId == pluginId && - other.schema == schema && - _deepEquality.equals(other.supportedContexts, supportedContexts) && - other.title == title; - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - (description.hashCode) + - (id.hashCode) + - (methodName.hashCode) + - (pluginId.hashCode) + - (schema == null ? 0 : schema!.hashCode) + - (supportedContexts.hashCode) + - (title.hashCode); - - @override - String toString() => 'PluginActionResponseDto[description=$description, id=$id, methodName=$methodName, pluginId=$pluginId, schema=$schema, supportedContexts=$supportedContexts, title=$title]'; - - Map toJson() { - final json = {}; - json[r'description'] = this.description; - json[r'id'] = this.id; - json[r'methodName'] = this.methodName; - json[r'pluginId'] = this.pluginId; - if (this.schema != null) { - json[r'schema'] = this.schema; - } else { - // json[r'schema'] = null; - } - json[r'supportedContexts'] = this.supportedContexts; - json[r'title'] = this.title; - return json; - } - - /// Returns a new [PluginActionResponseDto] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static PluginActionResponseDto? fromJson(dynamic value) { - upgradeDto(value, "PluginActionResponseDto"); - if (value is Map) { - final json = value.cast(); - - return PluginActionResponseDto( - description: mapValueOfType(json, r'description')!, - id: mapValueOfType(json, r'id')!, - methodName: mapValueOfType(json, r'methodName')!, - pluginId: mapValueOfType(json, r'pluginId')!, - schema: PluginJsonSchema.fromJson(json[r'schema']), - supportedContexts: PluginContextType.listFromJson(json[r'supportedContexts']), - title: mapValueOfType(json, r'title')!, - ); - } - return null; - } - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = PluginActionResponseDto.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } - - static Map mapFromJson(dynamic json) { - final map = {}; - if (json is Map && json.isNotEmpty) { - json = json.cast(); // ignore: parameter_assignments - for (final entry in json.entries) { - final value = PluginActionResponseDto.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of PluginActionResponseDto-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; - if (json is Map && json.isNotEmpty) { - // ignore: parameter_assignments - json = json.cast(); - for (final entry in json.entries) { - map[entry.key] = PluginActionResponseDto.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { - 'description', - 'id', - 'methodName', - 'pluginId', - 'schema', - 'supportedContexts', - 'title', - }; -} - diff --git a/mobile/openapi/lib/model/plugin_context_type.dart b/mobile/openapi/lib/model/plugin_context_type.dart deleted file mode 100644 index beda0b0f1a..0000000000 --- a/mobile/openapi/lib/model/plugin_context_type.dart +++ /dev/null @@ -1,88 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - -/// Plugin context -class PluginContextType { - /// Instantiate a new enum with the provided [value]. - const PluginContextType._(this.value); - - /// The underlying value of this enum member. - final String value; - - @override - String toString() => value; - - String toJson() => value; - - static const asset = PluginContextType._(r'asset'); - static const album = PluginContextType._(r'album'); - static const person = PluginContextType._(r'person'); - - /// List of all possible values in this [enum][PluginContextType]. - static const values = [ - asset, - album, - person, - ]; - - static PluginContextType? fromJson(dynamic value) => PluginContextTypeTypeTransformer().decode(value); - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = PluginContextType.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } -} - -/// Transformation class that can [encode] an instance of [PluginContextType] to String, -/// and [decode] dynamic data back to [PluginContextType]. -class PluginContextTypeTypeTransformer { - factory PluginContextTypeTypeTransformer() => _instance ??= const PluginContextTypeTypeTransformer._(); - - const PluginContextTypeTypeTransformer._(); - - String encode(PluginContextType data) => data.value; - - /// Decodes a [dynamic value][data] to a PluginContextType. - /// - /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, - /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] - /// cannot be decoded successfully, then an [UnimplementedError] is thrown. - /// - /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, - /// and users are still using an old app with the old code. - PluginContextType? decode(dynamic data, {bool allowNull = true}) { - if (data != null) { - switch (data) { - case r'asset': return PluginContextType.asset; - case r'album': return PluginContextType.album; - case r'person': return PluginContextType.person; - default: - if (!allowNull) { - throw ArgumentError('Unknown enum value to decode: $data'); - } - } - } - return null; - } - - /// Singleton [PluginContextTypeTypeTransformer] instance. - static PluginContextTypeTypeTransformer? _instance; -} - diff --git a/mobile/openapi/lib/model/plugin_filter_response_dto.dart b/mobile/openapi/lib/model/plugin_filter_response_dto.dart deleted file mode 100644 index d1ab867ff9..0000000000 --- a/mobile/openapi/lib/model/plugin_filter_response_dto.dart +++ /dev/null @@ -1,158 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - -class PluginFilterResponseDto { - /// Returns a new [PluginFilterResponseDto] instance. - PluginFilterResponseDto({ - required this.description, - required this.id, - required this.methodName, - required this.pluginId, - required this.schema, - this.supportedContexts = const [], - required this.title, - }); - - /// Filter description - String description; - - /// Filter ID - String id; - - /// Method name - String methodName; - - /// Plugin ID - String pluginId; - - /// Filter schema - PluginJsonSchema? schema; - - /// Supported contexts - List supportedContexts; - - /// Filter title - String title; - - @override - bool operator ==(Object other) => identical(this, other) || other is PluginFilterResponseDto && - other.description == description && - other.id == id && - other.methodName == methodName && - other.pluginId == pluginId && - other.schema == schema && - _deepEquality.equals(other.supportedContexts, supportedContexts) && - other.title == title; - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - (description.hashCode) + - (id.hashCode) + - (methodName.hashCode) + - (pluginId.hashCode) + - (schema == null ? 0 : schema!.hashCode) + - (supportedContexts.hashCode) + - (title.hashCode); - - @override - String toString() => 'PluginFilterResponseDto[description=$description, id=$id, methodName=$methodName, pluginId=$pluginId, schema=$schema, supportedContexts=$supportedContexts, title=$title]'; - - Map toJson() { - final json = {}; - json[r'description'] = this.description; - json[r'id'] = this.id; - json[r'methodName'] = this.methodName; - json[r'pluginId'] = this.pluginId; - if (this.schema != null) { - json[r'schema'] = this.schema; - } else { - // json[r'schema'] = null; - } - json[r'supportedContexts'] = this.supportedContexts; - json[r'title'] = this.title; - return json; - } - - /// Returns a new [PluginFilterResponseDto] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static PluginFilterResponseDto? fromJson(dynamic value) { - upgradeDto(value, "PluginFilterResponseDto"); - if (value is Map) { - final json = value.cast(); - - return PluginFilterResponseDto( - description: mapValueOfType(json, r'description')!, - id: mapValueOfType(json, r'id')!, - methodName: mapValueOfType(json, r'methodName')!, - pluginId: mapValueOfType(json, r'pluginId')!, - schema: PluginJsonSchema.fromJson(json[r'schema']), - supportedContexts: PluginContextType.listFromJson(json[r'supportedContexts']), - title: mapValueOfType(json, r'title')!, - ); - } - return null; - } - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = PluginFilterResponseDto.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } - - static Map mapFromJson(dynamic json) { - final map = {}; - if (json is Map && json.isNotEmpty) { - json = json.cast(); // ignore: parameter_assignments - for (final entry in json.entries) { - final value = PluginFilterResponseDto.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of PluginFilterResponseDto-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; - if (json is Map && json.isNotEmpty) { - // ignore: parameter_assignments - json = json.cast(); - for (final entry in json.entries) { - map[entry.key] = PluginFilterResponseDto.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { - 'description', - 'id', - 'methodName', - 'pluginId', - 'schema', - 'supportedContexts', - 'title', - }; -} - diff --git a/mobile/openapi/lib/model/plugin_json_schema.dart b/mobile/openapi/lib/model/plugin_json_schema.dart deleted file mode 100644 index f7a2d584d9..0000000000 --- a/mobile/openapi/lib/model/plugin_json_schema.dart +++ /dev/null @@ -1,158 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - -class PluginJsonSchema { - /// Returns a new [PluginJsonSchema] instance. - PluginJsonSchema({ - this.additionalProperties, - this.description, - this.properties = const {}, - this.required_ = const [], - this.type, - }); - - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - bool? additionalProperties; - - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - String? description; - - Map properties; - - List required_; - - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - PluginJsonSchemaType? type; - - @override - bool operator ==(Object other) => identical(this, other) || other is PluginJsonSchema && - other.additionalProperties == additionalProperties && - other.description == description && - _deepEquality.equals(other.properties, properties) && - _deepEquality.equals(other.required_, required_) && - other.type == type; - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - (additionalProperties == null ? 0 : additionalProperties!.hashCode) + - (description == null ? 0 : description!.hashCode) + - (properties.hashCode) + - (required_.hashCode) + - (type == null ? 0 : type!.hashCode); - - @override - String toString() => 'PluginJsonSchema[additionalProperties=$additionalProperties, description=$description, properties=$properties, required_=$required_, type=$type]'; - - Map toJson() { - final json = {}; - if (this.additionalProperties != null) { - json[r'additionalProperties'] = this.additionalProperties; - } else { - // json[r'additionalProperties'] = null; - } - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; - } - json[r'properties'] = this.properties; - json[r'required'] = this.required_; - if (this.type != null) { - json[r'type'] = this.type; - } else { - // json[r'type'] = null; - } - return json; - } - - /// Returns a new [PluginJsonSchema] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static PluginJsonSchema? fromJson(dynamic value) { - upgradeDto(value, "PluginJsonSchema"); - if (value is Map) { - final json = value.cast(); - - return PluginJsonSchema( - additionalProperties: mapValueOfType(json, r'additionalProperties'), - description: mapValueOfType(json, r'description'), - properties: PluginJsonSchemaProperty.mapFromJson(json[r'properties']), - required_: json[r'required'] is Iterable - ? (json[r'required'] as Iterable).cast().toList(growable: false) - : const [], - type: PluginJsonSchemaType.fromJson(json[r'type']), - ); - } - return null; - } - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = PluginJsonSchema.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } - - static Map mapFromJson(dynamic json) { - final map = {}; - if (json is Map && json.isNotEmpty) { - json = json.cast(); // ignore: parameter_assignments - for (final entry in json.entries) { - final value = PluginJsonSchema.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of PluginJsonSchema-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; - if (json is Map && json.isNotEmpty) { - // ignore: parameter_assignments - json = json.cast(); - for (final entry in json.entries) { - map[entry.key] = PluginJsonSchema.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { - }; -} - diff --git a/mobile/openapi/lib/model/plugin_json_schema_property.dart b/mobile/openapi/lib/model/plugin_json_schema_property.dart deleted file mode 100644 index 65951da0a3..0000000000 --- a/mobile/openapi/lib/model/plugin_json_schema_property.dart +++ /dev/null @@ -1,195 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - -class PluginJsonSchemaProperty { - /// Returns a new [PluginJsonSchemaProperty] instance. - PluginJsonSchemaProperty({ - this.additionalProperties, - this.default_, - this.description, - this.enum_ = const [], - this.items, - this.properties = const {}, - this.required_ = const [], - this.type, - }); - - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - PluginJsonSchemaPropertyAdditionalProperties? additionalProperties; - - Object? default_; - - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - String? description; - - List enum_; - - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - PluginJsonSchemaProperty? items; - - Map properties; - - List required_; - - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - PluginJsonSchemaType? type; - - @override - bool operator ==(Object other) => identical(this, other) || other is PluginJsonSchemaProperty && - other.additionalProperties == additionalProperties && - other.default_ == default_ && - other.description == description && - _deepEquality.equals(other.enum_, enum_) && - other.items == items && - _deepEquality.equals(other.properties, properties) && - _deepEquality.equals(other.required_, required_) && - other.type == type; - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - (additionalProperties == null ? 0 : additionalProperties!.hashCode) + - (default_ == null ? 0 : default_!.hashCode) + - (description == null ? 0 : description!.hashCode) + - (enum_.hashCode) + - (items == null ? 0 : items!.hashCode) + - (properties.hashCode) + - (required_.hashCode) + - (type == null ? 0 : type!.hashCode); - - @override - String toString() => 'PluginJsonSchemaProperty[additionalProperties=$additionalProperties, default_=$default_, description=$description, enum_=$enum_, items=$items, properties=$properties, required_=$required_, type=$type]'; - - Map toJson() { - final json = {}; - if (this.additionalProperties != null) { - json[r'additionalProperties'] = this.additionalProperties; - } else { - // json[r'additionalProperties'] = null; - } - if (this.default_ != null) { - json[r'default'] = this.default_; - } else { - // json[r'default'] = null; - } - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; - } - json[r'enum'] = this.enum_; - if (this.items != null) { - json[r'items'] = this.items; - } else { - // json[r'items'] = null; - } - json[r'properties'] = this.properties; - json[r'required'] = this.required_; - if (this.type != null) { - json[r'type'] = this.type; - } else { - // json[r'type'] = null; - } - return json; - } - - /// Returns a new [PluginJsonSchemaProperty] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static PluginJsonSchemaProperty? fromJson(dynamic value) { - upgradeDto(value, "PluginJsonSchemaProperty"); - if (value is Map) { - final json = value.cast(); - - return PluginJsonSchemaProperty( - additionalProperties: PluginJsonSchemaPropertyAdditionalProperties.fromJson(json[r'additionalProperties']), - default_: mapValueOfType(json, r'default'), - description: mapValueOfType(json, r'description'), - enum_: json[r'enum'] is Iterable - ? (json[r'enum'] as Iterable).cast().toList(growable: false) - : const [], - items: PluginJsonSchemaProperty.fromJson(json[r'items']), - properties: PluginJsonSchemaProperty.mapFromJson(json[r'properties']), - required_: json[r'required'] is Iterable - ? (json[r'required'] as Iterable).cast().toList(growable: false) - : const [], - type: PluginJsonSchemaType.fromJson(json[r'type']), - ); - } - return null; - } - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = PluginJsonSchemaProperty.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } - - static Map mapFromJson(dynamic json) { - final map = {}; - if (json is Map && json.isNotEmpty) { - json = json.cast(); // ignore: parameter_assignments - for (final entry in json.entries) { - final value = PluginJsonSchemaProperty.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of PluginJsonSchemaProperty-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; - if (json is Map && json.isNotEmpty) { - // ignore: parameter_assignments - json = json.cast(); - for (final entry in json.entries) { - map[entry.key] = PluginJsonSchemaProperty.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { - }; -} - diff --git a/mobile/openapi/lib/model/plugin_json_schema_property_additional_properties.dart b/mobile/openapi/lib/model/plugin_json_schema_property_additional_properties.dart deleted file mode 100644 index 169c6be772..0000000000 --- a/mobile/openapi/lib/model/plugin_json_schema_property_additional_properties.dart +++ /dev/null @@ -1,195 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - -class PluginJsonSchemaPropertyAdditionalProperties { - /// Returns a new [PluginJsonSchemaPropertyAdditionalProperties] instance. - PluginJsonSchemaPropertyAdditionalProperties({ - this.additionalProperties, - this.default_, - this.description, - this.enum_ = const [], - this.items, - this.properties = const {}, - this.required_ = const [], - this.type, - }); - - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - PluginJsonSchemaPropertyAdditionalProperties? additionalProperties; - - Object? default_; - - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - String? description; - - List enum_; - - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - PluginJsonSchemaProperty? items; - - Map properties; - - List required_; - - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - PluginJsonSchemaType? type; - - @override - bool operator ==(Object other) => identical(this, other) || other is PluginJsonSchemaPropertyAdditionalProperties && - other.additionalProperties == additionalProperties && - other.default_ == default_ && - other.description == description && - _deepEquality.equals(other.enum_, enum_) && - other.items == items && - _deepEquality.equals(other.properties, properties) && - _deepEquality.equals(other.required_, required_) && - other.type == type; - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - (additionalProperties == null ? 0 : additionalProperties!.hashCode) + - (default_ == null ? 0 : default_!.hashCode) + - (description == null ? 0 : description!.hashCode) + - (enum_.hashCode) + - (items == null ? 0 : items!.hashCode) + - (properties.hashCode) + - (required_.hashCode) + - (type == null ? 0 : type!.hashCode); - - @override - String toString() => 'PluginJsonSchemaPropertyAdditionalProperties[additionalProperties=$additionalProperties, default_=$default_, description=$description, enum_=$enum_, items=$items, properties=$properties, required_=$required_, type=$type]'; - - Map toJson() { - final json = {}; - if (this.additionalProperties != null) { - json[r'additionalProperties'] = this.additionalProperties; - } else { - // json[r'additionalProperties'] = null; - } - if (this.default_ != null) { - json[r'default'] = this.default_; - } else { - // json[r'default'] = null; - } - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; - } - json[r'enum'] = this.enum_; - if (this.items != null) { - json[r'items'] = this.items; - } else { - // json[r'items'] = null; - } - json[r'properties'] = this.properties; - json[r'required'] = this.required_; - if (this.type != null) { - json[r'type'] = this.type; - } else { - // json[r'type'] = null; - } - return json; - } - - /// Returns a new [PluginJsonSchemaPropertyAdditionalProperties] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static PluginJsonSchemaPropertyAdditionalProperties? fromJson(dynamic value) { - upgradeDto(value, "PluginJsonSchemaPropertyAdditionalProperties"); - if (value is Map) { - final json = value.cast(); - - return PluginJsonSchemaPropertyAdditionalProperties( - additionalProperties: PluginJsonSchemaPropertyAdditionalProperties.fromJson(json[r'additionalProperties']), - default_: mapValueOfType(json, r'default'), - description: mapValueOfType(json, r'description'), - enum_: json[r'enum'] is Iterable - ? (json[r'enum'] as Iterable).cast().toList(growable: false) - : const [], - items: PluginJsonSchemaProperty.fromJson(json[r'items']), - properties: PluginJsonSchemaProperty.mapFromJson(json[r'properties']), - required_: json[r'required'] is Iterable - ? (json[r'required'] as Iterable).cast().toList(growable: false) - : const [], - type: PluginJsonSchemaType.fromJson(json[r'type']), - ); - } - return null; - } - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = PluginJsonSchemaPropertyAdditionalProperties.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } - - static Map mapFromJson(dynamic json) { - final map = {}; - if (json is Map && json.isNotEmpty) { - json = json.cast(); // ignore: parameter_assignments - for (final entry in json.entries) { - final value = PluginJsonSchemaPropertyAdditionalProperties.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of PluginJsonSchemaPropertyAdditionalProperties-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; - if (json is Map && json.isNotEmpty) { - // ignore: parameter_assignments - json = json.cast(); - for (final entry in json.entries) { - map[entry.key] = PluginJsonSchemaPropertyAdditionalProperties.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { - }; -} - diff --git a/mobile/openapi/lib/model/plugin_json_schema_type.dart b/mobile/openapi/lib/model/plugin_json_schema_type.dart deleted file mode 100644 index cabac9b71b..0000000000 --- a/mobile/openapi/lib/model/plugin_json_schema_type.dart +++ /dev/null @@ -1,100 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - - -class PluginJsonSchemaType { - /// Instantiate a new enum with the provided [value]. - const PluginJsonSchemaType._(this.value); - - /// The underlying value of this enum member. - final String value; - - @override - String toString() => value; - - String toJson() => value; - - static const string = PluginJsonSchemaType._(r'string'); - static const number = PluginJsonSchemaType._(r'number'); - static const integer = PluginJsonSchemaType._(r'integer'); - static const boolean = PluginJsonSchemaType._(r'boolean'); - static const object = PluginJsonSchemaType._(r'object'); - static const array = PluginJsonSchemaType._(r'array'); - static const null_ = PluginJsonSchemaType._(r'null'); - - /// List of all possible values in this [enum][PluginJsonSchemaType]. - static const values = [ - string, - number, - integer, - boolean, - object, - array, - null_, - ]; - - static PluginJsonSchemaType? fromJson(dynamic value) => PluginJsonSchemaTypeTypeTransformer().decode(value); - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = PluginJsonSchemaType.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } -} - -/// Transformation class that can [encode] an instance of [PluginJsonSchemaType] to String, -/// and [decode] dynamic data back to [PluginJsonSchemaType]. -class PluginJsonSchemaTypeTypeTransformer { - factory PluginJsonSchemaTypeTypeTransformer() => _instance ??= const PluginJsonSchemaTypeTypeTransformer._(); - - const PluginJsonSchemaTypeTypeTransformer._(); - - String encode(PluginJsonSchemaType data) => data.value; - - /// Decodes a [dynamic value][data] to a PluginJsonSchemaType. - /// - /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, - /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] - /// cannot be decoded successfully, then an [UnimplementedError] is thrown. - /// - /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, - /// and users are still using an old app with the old code. - PluginJsonSchemaType? decode(dynamic data, {bool allowNull = true}) { - if (data != null) { - switch (data) { - case r'string': return PluginJsonSchemaType.string; - case r'number': return PluginJsonSchemaType.number; - case r'integer': return PluginJsonSchemaType.integer; - case r'boolean': return PluginJsonSchemaType.boolean; - case r'object': return PluginJsonSchemaType.object; - case r'array': return PluginJsonSchemaType.array; - case r'null': return PluginJsonSchemaType.null_; - default: - if (!allowNull) { - throw ArgumentError('Unknown enum value to decode: $data'); - } - } - } - return null; - } - - /// Singleton [PluginJsonSchemaTypeTypeTransformer] instance. - static PluginJsonSchemaTypeTypeTransformer? _instance; -} - diff --git a/mobile/openapi/lib/model/plugin_method_response_dto.dart b/mobile/openapi/lib/model/plugin_method_response_dto.dart new file mode 100644 index 0000000000..2887f4cc16 --- /dev/null +++ b/mobile/openapi/lib/model/plugin_method_response_dto.dart @@ -0,0 +1,172 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PluginMethodResponseDto { + /// Returns a new [PluginMethodResponseDto] instance. + PluginMethodResponseDto({ + required this.description, + required this.hostFunctions, + required this.key, + required this.name, + this.schema, + required this.title, + this.types = const [], + this.uiHints = const [], + }); + + /// Description + String description; + + bool hostFunctions; + + /// Key + String key; + + /// Name + String name; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + Object? schema; + + /// Title + String title; + + /// Workflow types + List types; + + /// Ui hints + List uiHints; + + @override + bool operator ==(Object other) => identical(this, other) || other is PluginMethodResponseDto && + other.description == description && + other.hostFunctions == hostFunctions && + other.key == key && + other.name == name && + other.schema == schema && + other.title == title && + _deepEquality.equals(other.types, types) && + _deepEquality.equals(other.uiHints, uiHints); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (description.hashCode) + + (hostFunctions.hashCode) + + (key.hashCode) + + (name.hashCode) + + (schema == null ? 0 : schema!.hashCode) + + (title.hashCode) + + (types.hashCode) + + (uiHints.hashCode); + + @override + String toString() => 'PluginMethodResponseDto[description=$description, hostFunctions=$hostFunctions, key=$key, name=$name, schema=$schema, title=$title, types=$types, uiHints=$uiHints]'; + + Map toJson() { + final json = {}; + json[r'description'] = this.description; + json[r'hostFunctions'] = this.hostFunctions; + json[r'key'] = this.key; + json[r'name'] = this.name; + if (this.schema != null) { + json[r'schema'] = this.schema; + } else { + // json[r'schema'] = null; + } + json[r'title'] = this.title; + json[r'types'] = this.types; + json[r'uiHints'] = this.uiHints; + return json; + } + + /// Returns a new [PluginMethodResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PluginMethodResponseDto? fromJson(dynamic value) { + upgradeDto(value, "PluginMethodResponseDto"); + if (value is Map) { + final json = value.cast(); + + return PluginMethodResponseDto( + description: mapValueOfType(json, r'description')!, + hostFunctions: mapValueOfType(json, r'hostFunctions')!, + key: mapValueOfType(json, r'key')!, + name: mapValueOfType(json, r'name')!, + schema: mapValueOfType(json, r'schema'), + title: mapValueOfType(json, r'title')!, + types: WorkflowType.listFromJson(json[r'types']), + uiHints: json[r'uiHints'] is Iterable + ? (json[r'uiHints'] as Iterable).cast().toList(growable: false) + : const [], + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PluginMethodResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PluginMethodResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PluginMethodResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PluginMethodResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'description', + 'hostFunctions', + 'key', + 'name', + 'title', + 'types', + 'uiHints', + }; +} + diff --git a/mobile/openapi/lib/model/plugin_response_dto.dart b/mobile/openapi/lib/model/plugin_response_dto.dart index 7a99896475..1bdb366f9e 100644 --- a/mobile/openapi/lib/model/plugin_response_dto.dart +++ b/mobile/openapi/lib/model/plugin_response_dto.dart @@ -13,21 +13,17 @@ part of openapi.api; class PluginResponseDto { /// Returns a new [PluginResponseDto] instance. PluginResponseDto({ - this.actions = const [], required this.author, required this.createdAt, required this.description, - this.filters = const [], required this.id, + this.methods = const [], required this.name, required this.title, required this.updatedAt, required this.version, }); - /// Plugin actions - List actions; - /// Plugin author String author; @@ -37,12 +33,12 @@ class PluginResponseDto { /// Plugin description String description; - /// Plugin filters - List filters; - /// Plugin ID String id; + /// Plugin methods + List methods; + /// Plugin name String name; @@ -57,12 +53,11 @@ class PluginResponseDto { @override bool operator ==(Object other) => identical(this, other) || other is PluginResponseDto && - _deepEquality.equals(other.actions, actions) && other.author == author && other.createdAt == createdAt && other.description == description && - _deepEquality.equals(other.filters, filters) && other.id == id && + _deepEquality.equals(other.methods, methods) && other.name == name && other.title == title && other.updatedAt == updatedAt && @@ -71,28 +66,26 @@ class PluginResponseDto { @override int get hashCode => // ignore: unnecessary_parenthesis - (actions.hashCode) + (author.hashCode) + (createdAt.hashCode) + (description.hashCode) + - (filters.hashCode) + (id.hashCode) + + (methods.hashCode) + (name.hashCode) + (title.hashCode) + (updatedAt.hashCode) + (version.hashCode); @override - String toString() => 'PluginResponseDto[actions=$actions, author=$author, createdAt=$createdAt, description=$description, filters=$filters, id=$id, name=$name, title=$title, updatedAt=$updatedAt, version=$version]'; + String toString() => 'PluginResponseDto[author=$author, createdAt=$createdAt, description=$description, id=$id, methods=$methods, name=$name, title=$title, updatedAt=$updatedAt, version=$version]'; Map toJson() { final json = {}; - json[r'actions'] = this.actions; json[r'author'] = this.author; json[r'createdAt'] = this.createdAt; json[r'description'] = this.description; - json[r'filters'] = this.filters; json[r'id'] = this.id; + json[r'methods'] = this.methods; json[r'name'] = this.name; json[r'title'] = this.title; json[r'updatedAt'] = this.updatedAt; @@ -109,12 +102,11 @@ class PluginResponseDto { final json = value.cast(); return PluginResponseDto( - actions: PluginActionResponseDto.listFromJson(json[r'actions']), author: mapValueOfType(json, r'author')!, createdAt: mapValueOfType(json, r'createdAt')!, description: mapValueOfType(json, r'description')!, - filters: PluginFilterResponseDto.listFromJson(json[r'filters']), id: mapValueOfType(json, r'id')!, + methods: PluginMethodResponseDto.listFromJson(json[r'methods']), name: mapValueOfType(json, r'name')!, title: mapValueOfType(json, r'title')!, updatedAt: mapValueOfType(json, r'updatedAt')!, @@ -166,12 +158,11 @@ class PluginResponseDto { /// The list of required keys that must be present in a JSON. static const requiredKeys = { - 'actions', 'author', 'createdAt', 'description', - 'filters', 'id', + 'methods', 'name', 'title', 'updatedAt', diff --git a/mobile/openapi/lib/model/plugin_trigger_response_dto.dart b/mobile/openapi/lib/model/plugin_trigger_response_dto.dart deleted file mode 100644 index a6ee1c6b69..0000000000 --- a/mobile/openapi/lib/model/plugin_trigger_response_dto.dart +++ /dev/null @@ -1,107 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - -class PluginTriggerResponseDto { - /// Returns a new [PluginTriggerResponseDto] instance. - PluginTriggerResponseDto({ - required this.contextType, - required this.type, - }); - - PluginContextType contextType; - - PluginTriggerType type; - - @override - bool operator ==(Object other) => identical(this, other) || other is PluginTriggerResponseDto && - other.contextType == contextType && - other.type == type; - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - (contextType.hashCode) + - (type.hashCode); - - @override - String toString() => 'PluginTriggerResponseDto[contextType=$contextType, type=$type]'; - - Map toJson() { - final json = {}; - json[r'contextType'] = this.contextType; - json[r'type'] = this.type; - return json; - } - - /// Returns a new [PluginTriggerResponseDto] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static PluginTriggerResponseDto? fromJson(dynamic value) { - upgradeDto(value, "PluginTriggerResponseDto"); - if (value is Map) { - final json = value.cast(); - - return PluginTriggerResponseDto( - contextType: PluginContextType.fromJson(json[r'contextType'])!, - type: PluginTriggerType.fromJson(json[r'type'])!, - ); - } - return null; - } - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = PluginTriggerResponseDto.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } - - static Map mapFromJson(dynamic json) { - final map = {}; - if (json is Map && json.isNotEmpty) { - json = json.cast(); // ignore: parameter_assignments - for (final entry in json.entries) { - final value = PluginTriggerResponseDto.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of PluginTriggerResponseDto-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; - if (json is Map && json.isNotEmpty) { - // ignore: parameter_assignments - json = json.cast(); - for (final entry in json.entries) { - map[entry.key] = PluginTriggerResponseDto.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { - 'contextType', - 'type', - }; -} - diff --git a/mobile/openapi/lib/model/workflow_action_item_dto.dart b/mobile/openapi/lib/model/workflow_action_item_dto.dart deleted file mode 100644 index 1ad70238d8..0000000000 --- a/mobile/openapi/lib/model/workflow_action_item_dto.dart +++ /dev/null @@ -1,107 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - -class WorkflowActionItemDto { - /// Returns a new [WorkflowActionItemDto] instance. - WorkflowActionItemDto({ - this.actionConfig = const {}, - required this.pluginActionId, - }); - - Map actionConfig; - - /// Plugin action ID - String pluginActionId; - - @override - bool operator ==(Object other) => identical(this, other) || other is WorkflowActionItemDto && - _deepEquality.equals(other.actionConfig, actionConfig) && - other.pluginActionId == pluginActionId; - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - (actionConfig.hashCode) + - (pluginActionId.hashCode); - - @override - String toString() => 'WorkflowActionItemDto[actionConfig=$actionConfig, pluginActionId=$pluginActionId]'; - - Map toJson() { - final json = {}; - json[r'actionConfig'] = this.actionConfig; - json[r'pluginActionId'] = this.pluginActionId; - return json; - } - - /// Returns a new [WorkflowActionItemDto] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static WorkflowActionItemDto? fromJson(dynamic value) { - upgradeDto(value, "WorkflowActionItemDto"); - if (value is Map) { - final json = value.cast(); - - return WorkflowActionItemDto( - actionConfig: mapCastOfType(json, r'actionConfig') ?? const {}, - pluginActionId: mapValueOfType(json, r'pluginActionId')!, - ); - } - return null; - } - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = WorkflowActionItemDto.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } - - static Map mapFromJson(dynamic json) { - final map = {}; - if (json is Map && json.isNotEmpty) { - json = json.cast(); // ignore: parameter_assignments - for (final entry in json.entries) { - final value = WorkflowActionItemDto.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of WorkflowActionItemDto-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; - if (json is Map && json.isNotEmpty) { - // ignore: parameter_assignments - json = json.cast(); - for (final entry in json.entries) { - map[entry.key] = WorkflowActionItemDto.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { - 'pluginActionId', - }; -} - diff --git a/mobile/openapi/lib/model/workflow_action_response_dto.dart b/mobile/openapi/lib/model/workflow_action_response_dto.dart deleted file mode 100644 index 999d9d86cb..0000000000 --- a/mobile/openapi/lib/model/workflow_action_response_dto.dart +++ /dev/null @@ -1,142 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - -class WorkflowActionResponseDto { - /// Returns a new [WorkflowActionResponseDto] instance. - WorkflowActionResponseDto({ - required this.actionConfig, - required this.id, - required this.order, - required this.pluginActionId, - required this.workflowId, - }); - - Map? actionConfig; - - /// Action ID - String id; - - /// Action order - /// - /// Minimum value: -9007199254740991 - /// Maximum value: 9007199254740991 - int order; - - /// Plugin action ID - String pluginActionId; - - /// Workflow ID - String workflowId; - - @override - bool operator ==(Object other) => identical(this, other) || other is WorkflowActionResponseDto && - _deepEquality.equals(other.actionConfig, actionConfig) && - other.id == id && - other.order == order && - other.pluginActionId == pluginActionId && - other.workflowId == workflowId; - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - (actionConfig == null ? 0 : actionConfig!.hashCode) + - (id.hashCode) + - (order.hashCode) + - (pluginActionId.hashCode) + - (workflowId.hashCode); - - @override - String toString() => 'WorkflowActionResponseDto[actionConfig=$actionConfig, id=$id, order=$order, pluginActionId=$pluginActionId, workflowId=$workflowId]'; - - Map toJson() { - final json = {}; - if (this.actionConfig != null) { - json[r'actionConfig'] = this.actionConfig; - } else { - // json[r'actionConfig'] = null; - } - json[r'id'] = this.id; - json[r'order'] = this.order; - json[r'pluginActionId'] = this.pluginActionId; - json[r'workflowId'] = this.workflowId; - return json; - } - - /// Returns a new [WorkflowActionResponseDto] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static WorkflowActionResponseDto? fromJson(dynamic value) { - upgradeDto(value, "WorkflowActionResponseDto"); - if (value is Map) { - final json = value.cast(); - - return WorkflowActionResponseDto( - actionConfig: mapCastOfType(json, r'actionConfig'), - id: mapValueOfType(json, r'id')!, - order: mapValueOfType(json, r'order')!, - pluginActionId: mapValueOfType(json, r'pluginActionId')!, - workflowId: mapValueOfType(json, r'workflowId')!, - ); - } - return null; - } - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = WorkflowActionResponseDto.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } - - static Map mapFromJson(dynamic json) { - final map = {}; - if (json is Map && json.isNotEmpty) { - json = json.cast(); // ignore: parameter_assignments - for (final entry in json.entries) { - final value = WorkflowActionResponseDto.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of WorkflowActionResponseDto-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; - if (json is Map && json.isNotEmpty) { - // ignore: parameter_assignments - json = json.cast(); - for (final entry in json.entries) { - map[entry.key] = WorkflowActionResponseDto.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { - 'actionConfig', - 'id', - 'order', - 'pluginActionId', - 'workflowId', - }; -} - diff --git a/mobile/openapi/lib/model/workflow_create_dto.dart b/mobile/openapi/lib/model/workflow_create_dto.dart index 143af0ca6c..dfd2d51290 100644 --- a/mobile/openapi/lib/model/workflow_create_dto.dart +++ b/mobile/openapi/lib/model/workflow_create_dto.dart @@ -13,24 +13,14 @@ part of openapi.api; class WorkflowCreateDto { /// Returns a new [WorkflowCreateDto] instance. WorkflowCreateDto({ - this.actions = const [], this.description, this.enabled, - this.filters = const [], - required this.name, - required this.triggerType, + this.name, + this.steps = const [], + required this.trigger, }); - /// Workflow actions - List actions; - /// Workflow description - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// String? description; /// Workflow enabled @@ -42,39 +32,35 @@ class WorkflowCreateDto { /// bool? enabled; - /// Workflow filters - List filters; - /// Workflow name - String name; + String? name; - PluginTriggerType triggerType; + List steps; + + WorkflowTrigger trigger; @override bool operator ==(Object other) => identical(this, other) || other is WorkflowCreateDto && - _deepEquality.equals(other.actions, actions) && other.description == description && other.enabled == enabled && - _deepEquality.equals(other.filters, filters) && other.name == name && - other.triggerType == triggerType; + _deepEquality.equals(other.steps, steps) && + other.trigger == trigger; @override int get hashCode => // ignore: unnecessary_parenthesis - (actions.hashCode) + (description == null ? 0 : description!.hashCode) + (enabled == null ? 0 : enabled!.hashCode) + - (filters.hashCode) + - (name.hashCode) + - (triggerType.hashCode); + (name == null ? 0 : name!.hashCode) + + (steps.hashCode) + + (trigger.hashCode); @override - String toString() => 'WorkflowCreateDto[actions=$actions, description=$description, enabled=$enabled, filters=$filters, name=$name, triggerType=$triggerType]'; + String toString() => 'WorkflowCreateDto[description=$description, enabled=$enabled, name=$name, steps=$steps, trigger=$trigger]'; Map toJson() { final json = {}; - json[r'actions'] = this.actions; if (this.description != null) { json[r'description'] = this.description; } else { @@ -85,9 +71,13 @@ class WorkflowCreateDto { } else { // json[r'enabled'] = null; } - json[r'filters'] = this.filters; + if (this.name != null) { json[r'name'] = this.name; - json[r'triggerType'] = this.triggerType; + } else { + // json[r'name'] = null; + } + json[r'steps'] = this.steps; + json[r'trigger'] = this.trigger; return json; } @@ -100,12 +90,11 @@ class WorkflowCreateDto { final json = value.cast(); return WorkflowCreateDto( - actions: WorkflowActionItemDto.listFromJson(json[r'actions']), description: mapValueOfType(json, r'description'), enabled: mapValueOfType(json, r'enabled'), - filters: WorkflowFilterItemDto.listFromJson(json[r'filters']), - name: mapValueOfType(json, r'name')!, - triggerType: PluginTriggerType.fromJson(json[r'triggerType'])!, + name: mapValueOfType(json, r'name'), + steps: WorkflowStepDto.listFromJson(json[r'steps']), + trigger: WorkflowTrigger.fromJson(json[r'trigger'])!, ); } return null; @@ -153,10 +142,7 @@ class WorkflowCreateDto { /// The list of required keys that must be present in a JSON. static const requiredKeys = { - 'actions', - 'filters', - 'name', - 'triggerType', + 'trigger', }; } diff --git a/mobile/openapi/lib/model/workflow_filter_item_dto.dart b/mobile/openapi/lib/model/workflow_filter_item_dto.dart deleted file mode 100644 index 92224b9f16..0000000000 --- a/mobile/openapi/lib/model/workflow_filter_item_dto.dart +++ /dev/null @@ -1,107 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - -class WorkflowFilterItemDto { - /// Returns a new [WorkflowFilterItemDto] instance. - WorkflowFilterItemDto({ - this.filterConfig = const {}, - required this.pluginFilterId, - }); - - Map filterConfig; - - /// Plugin filter ID - String pluginFilterId; - - @override - bool operator ==(Object other) => identical(this, other) || other is WorkflowFilterItemDto && - _deepEquality.equals(other.filterConfig, filterConfig) && - other.pluginFilterId == pluginFilterId; - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - (filterConfig.hashCode) + - (pluginFilterId.hashCode); - - @override - String toString() => 'WorkflowFilterItemDto[filterConfig=$filterConfig, pluginFilterId=$pluginFilterId]'; - - Map toJson() { - final json = {}; - json[r'filterConfig'] = this.filterConfig; - json[r'pluginFilterId'] = this.pluginFilterId; - return json; - } - - /// Returns a new [WorkflowFilterItemDto] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static WorkflowFilterItemDto? fromJson(dynamic value) { - upgradeDto(value, "WorkflowFilterItemDto"); - if (value is Map) { - final json = value.cast(); - - return WorkflowFilterItemDto( - filterConfig: mapCastOfType(json, r'filterConfig') ?? const {}, - pluginFilterId: mapValueOfType(json, r'pluginFilterId')!, - ); - } - return null; - } - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = WorkflowFilterItemDto.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } - - static Map mapFromJson(dynamic json) { - final map = {}; - if (json is Map && json.isNotEmpty) { - json = json.cast(); // ignore: parameter_assignments - for (final entry in json.entries) { - final value = WorkflowFilterItemDto.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of WorkflowFilterItemDto-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; - if (json is Map && json.isNotEmpty) { - // ignore: parameter_assignments - json = json.cast(); - for (final entry in json.entries) { - map[entry.key] = WorkflowFilterItemDto.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { - 'pluginFilterId', - }; -} - diff --git a/mobile/openapi/lib/model/workflow_filter_response_dto.dart b/mobile/openapi/lib/model/workflow_filter_response_dto.dart deleted file mode 100644 index b9a841a68b..0000000000 --- a/mobile/openapi/lib/model/workflow_filter_response_dto.dart +++ /dev/null @@ -1,142 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - -class WorkflowFilterResponseDto { - /// Returns a new [WorkflowFilterResponseDto] instance. - WorkflowFilterResponseDto({ - required this.filterConfig, - required this.id, - required this.order, - required this.pluginFilterId, - required this.workflowId, - }); - - Map? filterConfig; - - /// Filter ID - String id; - - /// Filter order - /// - /// Minimum value: -9007199254740991 - /// Maximum value: 9007199254740991 - int order; - - /// Plugin filter ID - String pluginFilterId; - - /// Workflow ID - String workflowId; - - @override - bool operator ==(Object other) => identical(this, other) || other is WorkflowFilterResponseDto && - _deepEquality.equals(other.filterConfig, filterConfig) && - other.id == id && - other.order == order && - other.pluginFilterId == pluginFilterId && - other.workflowId == workflowId; - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - (filterConfig == null ? 0 : filterConfig!.hashCode) + - (id.hashCode) + - (order.hashCode) + - (pluginFilterId.hashCode) + - (workflowId.hashCode); - - @override - String toString() => 'WorkflowFilterResponseDto[filterConfig=$filterConfig, id=$id, order=$order, pluginFilterId=$pluginFilterId, workflowId=$workflowId]'; - - Map toJson() { - final json = {}; - if (this.filterConfig != null) { - json[r'filterConfig'] = this.filterConfig; - } else { - // json[r'filterConfig'] = null; - } - json[r'id'] = this.id; - json[r'order'] = this.order; - json[r'pluginFilterId'] = this.pluginFilterId; - json[r'workflowId'] = this.workflowId; - return json; - } - - /// Returns a new [WorkflowFilterResponseDto] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static WorkflowFilterResponseDto? fromJson(dynamic value) { - upgradeDto(value, "WorkflowFilterResponseDto"); - if (value is Map) { - final json = value.cast(); - - return WorkflowFilterResponseDto( - filterConfig: mapCastOfType(json, r'filterConfig'), - id: mapValueOfType(json, r'id')!, - order: mapValueOfType(json, r'order')!, - pluginFilterId: mapValueOfType(json, r'pluginFilterId')!, - workflowId: mapValueOfType(json, r'workflowId')!, - ); - } - return null; - } - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = WorkflowFilterResponseDto.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } - - static Map mapFromJson(dynamic json) { - final map = {}; - if (json is Map && json.isNotEmpty) { - json = json.cast(); // ignore: parameter_assignments - for (final entry in json.entries) { - final value = WorkflowFilterResponseDto.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of WorkflowFilterResponseDto-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; - if (json is Map && json.isNotEmpty) { - // ignore: parameter_assignments - json = json.cast(); - for (final entry in json.entries) { - map[entry.key] = WorkflowFilterResponseDto.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { - 'filterConfig', - 'id', - 'order', - 'pluginFilterId', - 'workflowId', - }; -} - diff --git a/mobile/openapi/lib/model/workflow_response_dto.dart b/mobile/openapi/lib/model/workflow_response_dto.dart index 6461b62508..f44506d69d 100644 --- a/mobile/openapi/lib/model/workflow_response_dto.dart +++ b/mobile/openapi/lib/model/workflow_response_dto.dart @@ -13,86 +13,83 @@ part of openapi.api; class WorkflowResponseDto { /// Returns a new [WorkflowResponseDto] instance. WorkflowResponseDto({ - this.actions = const [], required this.createdAt, required this.description, required this.enabled, - this.filters = const [], required this.id, required this.name, - required this.ownerId, - required this.triggerType, + this.steps = const [], + required this.trigger, + required this.updatedAt, }); - /// Workflow actions - List actions; - /// Creation date String createdAt; /// Workflow description - String description; + String? description; /// Workflow enabled bool enabled; - /// Workflow filters - List filters; - /// Workflow ID String id; /// Workflow name String? name; - /// Owner user ID - String ownerId; + /// Workflow steps + List steps; - PluginTriggerType triggerType; + WorkflowTrigger trigger; + + /// Update date + String updatedAt; @override bool operator ==(Object other) => identical(this, other) || other is WorkflowResponseDto && - _deepEquality.equals(other.actions, actions) && other.createdAt == createdAt && other.description == description && other.enabled == enabled && - _deepEquality.equals(other.filters, filters) && other.id == id && other.name == name && - other.ownerId == ownerId && - other.triggerType == triggerType; + _deepEquality.equals(other.steps, steps) && + other.trigger == trigger && + other.updatedAt == updatedAt; @override int get hashCode => // ignore: unnecessary_parenthesis - (actions.hashCode) + (createdAt.hashCode) + - (description.hashCode) + + (description == null ? 0 : description!.hashCode) + (enabled.hashCode) + - (filters.hashCode) + (id.hashCode) + (name == null ? 0 : name!.hashCode) + - (ownerId.hashCode) + - (triggerType.hashCode); + (steps.hashCode) + + (trigger.hashCode) + + (updatedAt.hashCode); @override - String toString() => 'WorkflowResponseDto[actions=$actions, createdAt=$createdAt, description=$description, enabled=$enabled, filters=$filters, id=$id, name=$name, ownerId=$ownerId, triggerType=$triggerType]'; + String toString() => 'WorkflowResponseDto[createdAt=$createdAt, description=$description, enabled=$enabled, id=$id, name=$name, steps=$steps, trigger=$trigger, updatedAt=$updatedAt]'; Map toJson() { final json = {}; - json[r'actions'] = this.actions; json[r'createdAt'] = this.createdAt; + if (this.description != null) { json[r'description'] = this.description; + } else { + // json[r'description'] = null; + } json[r'enabled'] = this.enabled; - json[r'filters'] = this.filters; json[r'id'] = this.id; if (this.name != null) { json[r'name'] = this.name; } else { // json[r'name'] = null; } - json[r'ownerId'] = this.ownerId; - json[r'triggerType'] = this.triggerType; + json[r'steps'] = this.steps; + json[r'trigger'] = this.trigger; + json[r'updatedAt'] = this.updatedAt; return json; } @@ -105,15 +102,14 @@ class WorkflowResponseDto { final json = value.cast(); return WorkflowResponseDto( - actions: WorkflowActionResponseDto.listFromJson(json[r'actions']), createdAt: mapValueOfType(json, r'createdAt')!, - description: mapValueOfType(json, r'description')!, + description: mapValueOfType(json, r'description'), enabled: mapValueOfType(json, r'enabled')!, - filters: WorkflowFilterResponseDto.listFromJson(json[r'filters']), id: mapValueOfType(json, r'id')!, name: mapValueOfType(json, r'name'), - ownerId: mapValueOfType(json, r'ownerId')!, - triggerType: PluginTriggerType.fromJson(json[r'triggerType'])!, + steps: WorkflowStepDto.listFromJson(json[r'steps']), + trigger: WorkflowTrigger.fromJson(json[r'trigger'])!, + updatedAt: mapValueOfType(json, r'updatedAt')!, ); } return null; @@ -161,15 +157,14 @@ class WorkflowResponseDto { /// The list of required keys that must be present in a JSON. static const requiredKeys = { - 'actions', 'createdAt', 'description', 'enabled', - 'filters', 'id', 'name', - 'ownerId', - 'triggerType', + 'steps', + 'trigger', + 'updatedAt', }; } diff --git a/mobile/openapi/lib/model/workflow_share_response_dto.dart b/mobile/openapi/lib/model/workflow_share_response_dto.dart new file mode 100644 index 0000000000..336e8503c5 --- /dev/null +++ b/mobile/openapi/lib/model/workflow_share_response_dto.dart @@ -0,0 +1,134 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class WorkflowShareResponseDto { + /// Returns a new [WorkflowShareResponseDto] instance. + WorkflowShareResponseDto({ + required this.description, + required this.name, + this.steps = const [], + required this.trigger, + }); + + /// Workflow description + String? description; + + /// Workflow name + String? name; + + /// Workflow steps + List steps; + + WorkflowTrigger trigger; + + @override + bool operator ==(Object other) => identical(this, other) || other is WorkflowShareResponseDto && + other.description == description && + other.name == name && + _deepEquality.equals(other.steps, steps) && + other.trigger == trigger; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (description == null ? 0 : description!.hashCode) + + (name == null ? 0 : name!.hashCode) + + (steps.hashCode) + + (trigger.hashCode); + + @override + String toString() => 'WorkflowShareResponseDto[description=$description, name=$name, steps=$steps, trigger=$trigger]'; + + Map toJson() { + final json = {}; + if (this.description != null) { + json[r'description'] = this.description; + } else { + // json[r'description'] = null; + } + if (this.name != null) { + json[r'name'] = this.name; + } else { + // json[r'name'] = null; + } + json[r'steps'] = this.steps; + json[r'trigger'] = this.trigger; + return json; + } + + /// Returns a new [WorkflowShareResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static WorkflowShareResponseDto? fromJson(dynamic value) { + upgradeDto(value, "WorkflowShareResponseDto"); + if (value is Map) { + final json = value.cast(); + + return WorkflowShareResponseDto( + description: mapValueOfType(json, r'description'), + name: mapValueOfType(json, r'name'), + steps: WorkflowShareStepDto.listFromJson(json[r'steps']), + trigger: WorkflowTrigger.fromJson(json[r'trigger'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = WorkflowShareResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = WorkflowShareResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of WorkflowShareResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = WorkflowShareResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'description', + 'name', + 'steps', + 'trigger', + }; +} + diff --git a/mobile/openapi/lib/model/workflow_share_step_dto.dart b/mobile/openapi/lib/model/workflow_share_step_dto.dart new file mode 100644 index 0000000000..79c55ef716 --- /dev/null +++ b/mobile/openapi/lib/model/workflow_share_step_dto.dart @@ -0,0 +1,131 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class WorkflowShareStepDto { + /// Returns a new [WorkflowShareStepDto] instance. + WorkflowShareStepDto({ + this.config = const {}, + this.enabled, + required this.method, + }); + + /// Step configuration + Map? config; + + /// Step is enabled + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? enabled; + + /// Step plugin method + String method; + + @override + bool operator ==(Object other) => identical(this, other) || other is WorkflowShareStepDto && + _deepEquality.equals(other.config, config) && + other.enabled == enabled && + other.method == method; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (config == null ? 0 : config!.hashCode) + + (enabled == null ? 0 : enabled!.hashCode) + + (method.hashCode); + + @override + String toString() => 'WorkflowShareStepDto[config=$config, enabled=$enabled, method=$method]'; + + Map toJson() { + final json = {}; + if (this.config != null) { + json[r'config'] = this.config; + } else { + // json[r'config'] = null; + } + if (this.enabled != null) { + json[r'enabled'] = this.enabled; + } else { + // json[r'enabled'] = null; + } + json[r'method'] = this.method; + return json; + } + + /// Returns a new [WorkflowShareStepDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static WorkflowShareStepDto? fromJson(dynamic value) { + upgradeDto(value, "WorkflowShareStepDto"); + if (value is Map) { + final json = value.cast(); + + return WorkflowShareStepDto( + config: mapCastOfType(json, r'config'), + enabled: mapValueOfType(json, r'enabled'), + method: mapValueOfType(json, r'method')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = WorkflowShareStepDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = WorkflowShareStepDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of WorkflowShareStepDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = WorkflowShareStepDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'config', + 'method', + }; +} + diff --git a/mobile/openapi/lib/model/workflow_step_dto.dart b/mobile/openapi/lib/model/workflow_step_dto.dart new file mode 100644 index 0000000000..e881ad3150 --- /dev/null +++ b/mobile/openapi/lib/model/workflow_step_dto.dart @@ -0,0 +1,131 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class WorkflowStepDto { + /// Returns a new [WorkflowStepDto] instance. + WorkflowStepDto({ + this.config = const {}, + this.enabled, + required this.method, + }); + + /// Step configuration + Map? config; + + /// Step is enabled + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? enabled; + + /// Step plugin method + String method; + + @override + bool operator ==(Object other) => identical(this, other) || other is WorkflowStepDto && + _deepEquality.equals(other.config, config) && + other.enabled == enabled && + other.method == method; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (config == null ? 0 : config!.hashCode) + + (enabled == null ? 0 : enabled!.hashCode) + + (method.hashCode); + + @override + String toString() => 'WorkflowStepDto[config=$config, enabled=$enabled, method=$method]'; + + Map toJson() { + final json = {}; + if (this.config != null) { + json[r'config'] = this.config; + } else { + // json[r'config'] = null; + } + if (this.enabled != null) { + json[r'enabled'] = this.enabled; + } else { + // json[r'enabled'] = null; + } + json[r'method'] = this.method; + return json; + } + + /// Returns a new [WorkflowStepDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static WorkflowStepDto? fromJson(dynamic value) { + upgradeDto(value, "WorkflowStepDto"); + if (value is Map) { + final json = value.cast(); + + return WorkflowStepDto( + config: mapCastOfType(json, r'config'), + enabled: mapValueOfType(json, r'enabled'), + method: mapValueOfType(json, r'method')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = WorkflowStepDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = WorkflowStepDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of WorkflowStepDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = WorkflowStepDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'config', + 'method', + }; +} + diff --git a/mobile/openapi/lib/model/plugin_trigger_type.dart b/mobile/openapi/lib/model/workflow_trigger.dart similarity index 50% rename from mobile/openapi/lib/model/plugin_trigger_type.dart rename to mobile/openapi/lib/model/workflow_trigger.dart index 3ebcef7a95..47bf95e05e 100644 --- a/mobile/openapi/lib/model/plugin_trigger_type.dart +++ b/mobile/openapi/lib/model/workflow_trigger.dart @@ -11,9 +11,9 @@ part of openapi.api; /// Plugin trigger type -class PluginTriggerType { +class WorkflowTrigger { /// Instantiate a new enum with the provided [value]. - const PluginTriggerType._(this.value); + const WorkflowTrigger._(this.value); /// The underlying value of this enum member. final String value; @@ -23,22 +23,22 @@ class PluginTriggerType { String toJson() => value; - static const assetCreate = PluginTriggerType._(r'AssetCreate'); - static const personRecognized = PluginTriggerType._(r'PersonRecognized'); + static const assetCreate = WorkflowTrigger._(r'AssetCreate'); + static const personRecognized = WorkflowTrigger._(r'PersonRecognized'); - /// List of all possible values in this [enum][PluginTriggerType]. - static const values = [ + /// List of all possible values in this [enum][WorkflowTrigger]. + static const values = [ assetCreate, personRecognized, ]; - static PluginTriggerType? fromJson(dynamic value) => PluginTriggerTypeTypeTransformer().decode(value); + static WorkflowTrigger? fromJson(dynamic value) => WorkflowTriggerTypeTransformer().decode(value); - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; if (json is List && json.isNotEmpty) { for (final row in json) { - final value = PluginTriggerType.fromJson(row); + final value = WorkflowTrigger.fromJson(row); if (value != null) { result.add(value); } @@ -48,16 +48,16 @@ class PluginTriggerType { } } -/// Transformation class that can [encode] an instance of [PluginTriggerType] to String, -/// and [decode] dynamic data back to [PluginTriggerType]. -class PluginTriggerTypeTypeTransformer { - factory PluginTriggerTypeTypeTransformer() => _instance ??= const PluginTriggerTypeTypeTransformer._(); +/// Transformation class that can [encode] an instance of [WorkflowTrigger] to String, +/// and [decode] dynamic data back to [WorkflowTrigger]. +class WorkflowTriggerTypeTransformer { + factory WorkflowTriggerTypeTransformer() => _instance ??= const WorkflowTriggerTypeTransformer._(); - const PluginTriggerTypeTypeTransformer._(); + const WorkflowTriggerTypeTransformer._(); - String encode(PluginTriggerType data) => data.value; + String encode(WorkflowTrigger data) => data.value; - /// Decodes a [dynamic value][data] to a PluginTriggerType. + /// Decodes a [dynamic value][data] to a WorkflowTrigger. /// /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] @@ -65,11 +65,11 @@ class PluginTriggerTypeTypeTransformer { /// /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, /// and users are still using an old app with the old code. - PluginTriggerType? decode(dynamic data, {bool allowNull = true}) { + WorkflowTrigger? decode(dynamic data, {bool allowNull = true}) { if (data != null) { switch (data) { - case r'AssetCreate': return PluginTriggerType.assetCreate; - case r'PersonRecognized': return PluginTriggerType.personRecognized; + case r'AssetCreate': return WorkflowTrigger.assetCreate; + case r'PersonRecognized': return WorkflowTrigger.personRecognized; default: if (!allowNull) { throw ArgumentError('Unknown enum value to decode: $data'); @@ -79,7 +79,7 @@ class PluginTriggerTypeTypeTransformer { return null; } - /// Singleton [PluginTriggerTypeTypeTransformer] instance. - static PluginTriggerTypeTypeTransformer? _instance; + /// Singleton [WorkflowTriggerTypeTransformer] instance. + static WorkflowTriggerTypeTransformer? _instance; } diff --git a/mobile/openapi/lib/model/workflow_trigger_response_dto.dart b/mobile/openapi/lib/model/workflow_trigger_response_dto.dart new file mode 100644 index 0000000000..6e24e1559a --- /dev/null +++ b/mobile/openapi/lib/model/workflow_trigger_response_dto.dart @@ -0,0 +1,108 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class WorkflowTriggerResponseDto { + /// Returns a new [WorkflowTriggerResponseDto] instance. + WorkflowTriggerResponseDto({ + required this.trigger, + this.types = const [], + }); + + WorkflowTrigger trigger; + + /// Workflow types + List types; + + @override + bool operator ==(Object other) => identical(this, other) || other is WorkflowTriggerResponseDto && + other.trigger == trigger && + _deepEquality.equals(other.types, types); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (trigger.hashCode) + + (types.hashCode); + + @override + String toString() => 'WorkflowTriggerResponseDto[trigger=$trigger, types=$types]'; + + Map toJson() { + final json = {}; + json[r'trigger'] = this.trigger; + json[r'types'] = this.types; + return json; + } + + /// Returns a new [WorkflowTriggerResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static WorkflowTriggerResponseDto? fromJson(dynamic value) { + upgradeDto(value, "WorkflowTriggerResponseDto"); + if (value is Map) { + final json = value.cast(); + + return WorkflowTriggerResponseDto( + trigger: WorkflowTrigger.fromJson(json[r'trigger'])!, + types: WorkflowType.listFromJson(json[r'types']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = WorkflowTriggerResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = WorkflowTriggerResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of WorkflowTriggerResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = WorkflowTriggerResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'trigger', + 'types', + }; +} + diff --git a/mobile/openapi/lib/model/workflow_type.dart b/mobile/openapi/lib/model/workflow_type.dart new file mode 100644 index 0000000000..c18b07e9fb --- /dev/null +++ b/mobile/openapi/lib/model/workflow_type.dart @@ -0,0 +1,85 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Workflow type +class WorkflowType { + /// Instantiate a new enum with the provided [value]. + const WorkflowType._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const assetV1 = WorkflowType._(r'AssetV1'); + static const assetPersonV1 = WorkflowType._(r'AssetPersonV1'); + + /// List of all possible values in this [enum][WorkflowType]. + static const values = [ + assetV1, + assetPersonV1, + ]; + + static WorkflowType? fromJson(dynamic value) => WorkflowTypeTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = WorkflowType.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [WorkflowType] to String, +/// and [decode] dynamic data back to [WorkflowType]. +class WorkflowTypeTypeTransformer { + factory WorkflowTypeTypeTransformer() => _instance ??= const WorkflowTypeTypeTransformer._(); + + const WorkflowTypeTypeTransformer._(); + + String encode(WorkflowType data) => data.value; + + /// Decodes a [dynamic value][data] to a WorkflowType. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + WorkflowType? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'AssetV1': return WorkflowType.assetV1; + case r'AssetPersonV1': return WorkflowType.assetPersonV1; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [WorkflowTypeTypeTransformer] instance. + static WorkflowTypeTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/workflow_update_dto.dart b/mobile/openapi/lib/model/workflow_update_dto.dart index 9abb45ddd5..0bce75283a 100644 --- a/mobile/openapi/lib/model/workflow_update_dto.dart +++ b/mobile/openapi/lib/model/workflow_update_dto.dart @@ -13,24 +13,14 @@ part of openapi.api; class WorkflowUpdateDto { /// Returns a new [WorkflowUpdateDto] instance. WorkflowUpdateDto({ - this.actions = const [], this.description, this.enabled, - this.filters = const [], this.name, - this.triggerType, + this.steps = const [], + this.trigger, }); - /// Workflow actions - List actions; - /// Workflow description - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// String? description; /// Workflow enabled @@ -42,51 +32,41 @@ class WorkflowUpdateDto { /// bool? enabled; - /// Workflow filters - List filters; - /// Workflow name - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// String? name; + List steps; + /// /// Please note: This property should have been non-nullable! Since the specification file /// does not include a default value (using the "default:" property), however, the generated /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - PluginTriggerType? triggerType; + WorkflowTrigger? trigger; @override bool operator ==(Object other) => identical(this, other) || other is WorkflowUpdateDto && - _deepEquality.equals(other.actions, actions) && other.description == description && other.enabled == enabled && - _deepEquality.equals(other.filters, filters) && other.name == name && - other.triggerType == triggerType; + _deepEquality.equals(other.steps, steps) && + other.trigger == trigger; @override int get hashCode => // ignore: unnecessary_parenthesis - (actions.hashCode) + (description == null ? 0 : description!.hashCode) + (enabled == null ? 0 : enabled!.hashCode) + - (filters.hashCode) + (name == null ? 0 : name!.hashCode) + - (triggerType == null ? 0 : triggerType!.hashCode); + (steps.hashCode) + + (trigger == null ? 0 : trigger!.hashCode); @override - String toString() => 'WorkflowUpdateDto[actions=$actions, description=$description, enabled=$enabled, filters=$filters, name=$name, triggerType=$triggerType]'; + String toString() => 'WorkflowUpdateDto[description=$description, enabled=$enabled, name=$name, steps=$steps, trigger=$trigger]'; Map toJson() { final json = {}; - json[r'actions'] = this.actions; if (this.description != null) { json[r'description'] = this.description; } else { @@ -97,16 +77,16 @@ class WorkflowUpdateDto { } else { // json[r'enabled'] = null; } - json[r'filters'] = this.filters; if (this.name != null) { json[r'name'] = this.name; } else { // json[r'name'] = null; } - if (this.triggerType != null) { - json[r'triggerType'] = this.triggerType; + json[r'steps'] = this.steps; + if (this.trigger != null) { + json[r'trigger'] = this.trigger; } else { - // json[r'triggerType'] = null; + // json[r'trigger'] = null; } return json; } @@ -120,12 +100,11 @@ class WorkflowUpdateDto { final json = value.cast(); return WorkflowUpdateDto( - actions: WorkflowActionItemDto.listFromJson(json[r'actions']), description: mapValueOfType(json, r'description'), enabled: mapValueOfType(json, r'enabled'), - filters: WorkflowFilterItemDto.listFromJson(json[r'filters']), name: mapValueOfType(json, r'name'), - triggerType: PluginTriggerType.fromJson(json[r'triggerType']), + steps: WorkflowStepDto.listFromJson(json[r'steps']), + trigger: WorkflowTrigger.fromJson(json[r'trigger']), ); } return null; diff --git a/mobile/packages/ui/lib/immich_ui.dart b/mobile/packages/ui/lib/immich_ui.dart index c9e510a162..6158b4f0e1 100644 --- a/mobile/packages/ui/lib/immich_ui.dart +++ b/mobile/packages/ui/lib/immich_ui.dart @@ -5,6 +5,7 @@ export 'src/components/icon_button.dart'; export 'src/components/password_input.dart'; export 'src/components/text_button.dart'; export 'src/components/text_input.dart'; +export 'src/components/url_input.dart'; export 'src/constants.dart'; export 'src/theme.dart'; export 'src/translation.dart'; diff --git a/mobile/packages/ui/lib/src/components/form.dart b/mobile/packages/ui/lib/src/components/form.dart index 9e8c161806..4d0344ae6b 100644 --- a/mobile/packages/ui/lib/src/components/form.dart +++ b/mobile/packages/ui/lib/src/components/form.dart @@ -4,95 +4,95 @@ import 'package:flutter/material.dart'; import 'package:immich_ui/immich_ui.dart'; import 'package:immich_ui/src/internal.dart'; -class ImmichForm extends StatefulWidget { - final String? submitText; - final IconData? submitIcon; - final FutureOr Function()? onSubmit; - final Widget child; +class ImmichFormController extends ChangeNotifier { + ImmichFormController({this.onSubmit}); - const ImmichForm({ - super.key, - this.submitText, - this.submitIcon, - required this.onSubmit, - required this.child, - }); + FutureOr Function()? onSubmit; + final formKey = GlobalKey(); - @override - State createState() => ImmichFormState(); - - static ImmichFormState of(BuildContext context) { - final scope = context.dependOnInheritedWidgetOfExactType<_ImmichFormScope>(); - if (scope == null) { - throw FlutterError( - 'ImmichForm.of() called with a context that does not contain an ImmichForm.\n' - 'No ImmichForm ancestor could be found starting from the context that was passed to ' - 'ImmichForm.of(). This usually happens when the context provided is ' - 'from a widget above the ImmichForm.\n' - 'The context used was:\n' - '$context', - ); - } - return scope._formState; - } -} - -class ImmichFormState extends State { - final _formKey = GlobalKey(); bool _isLoading = false; + bool get isLoading => _isLoading; - FutureOr submit() async { - final isValid = _formKey.currentState?.validate() ?? false; - if (!isValid) { + Future submit() async { + if (_isLoading) { + return; + } + if (!(formKey.currentState?.validate() ?? false)) { return; } - setState(() { - _isLoading = true; - }); - + _isLoading = true; + notifyListeners(); try { - await widget.onSubmit?.call(); + await onSubmit?.call(); } finally { - if (mounted) { - setState(() { - _isLoading = false; - }); - } + _isLoading = false; + notifyListeners(); } } +} + +class ImmichForm extends StatefulWidget { + final FutureOr Function()? onSubmit; + final Widget Function(BuildContext context, ImmichFormController form) builder; + final String? submitText; + final IconData? submitIcon; + + const ImmichForm({ + super.key, + this.onSubmit, + this.submitText, + this.submitIcon, + required this.builder, + }); + + @override + State createState() => _ImmichFormState(); +} + +class _ImmichFormState extends State { + late final ImmichFormController _controller; + + @override + void initState() { + super.initState(); + _controller = ImmichFormController(onSubmit: widget.onSubmit); + } + + @override + void didUpdateWidget(ImmichForm oldWidget) { + super.didUpdateWidget(oldWidget); + _controller.onSubmit = widget.onSubmit; + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } @override Widget build(BuildContext context) { final submitText = widget.submitText ?? context.translations.submit; - return _ImmichFormScope( - formState: this, - child: Form( - key: _formKey, - child: Column( - spacing: ImmichSpacing.md, - children: [ - widget.child, - ImmichTextButton( + return Form( + key: _controller.formKey, + child: Column( + spacing: ImmichSpacing.md, + children: [ + widget.builder(context, _controller), + ListenableBuilder( + listenable: _controller, + builder: (context, _) => ImmichTextButton( labelText: submitText, icon: widget.submitIcon, variant: ImmichVariant.filled, - loading: _isLoading, - onPressed: submit, - disabled: widget.onSubmit == null, + loading: _controller.isLoading, + onPressed: _controller.submit, + disabled: _controller.onSubmit == null, ), - ], - ), + ), + ], ), ); } } - -class _ImmichFormScope extends InheritedWidget { - const _ImmichFormScope({required super.child, required ImmichFormState formState}) : _formState = formState; - - final ImmichFormState _formState; - - @override - bool updateShouldNotify(_ImmichFormScope oldWidget) => oldWidget._formState != _formState; -} diff --git a/mobile/packages/ui/lib/src/components/password_input.dart b/mobile/packages/ui/lib/src/components/password_input.dart index bd5a149354..70808472df 100644 --- a/mobile/packages/ui/lib/src/components/password_input.dart +++ b/mobile/packages/ui/lib/src/components/password_input.dart @@ -8,7 +8,7 @@ class ImmichPasswordInput extends StatefulWidget { final TextEditingController? controller; final FocusNode? focusNode; final String? Function(String?)? validator; - final void Function(BuildContext, String)? onSubmit; + final void Function(String value)? onSubmit; final TextInputAction? keyboardAction; const ImmichPasswordInput({ diff --git a/mobile/packages/ui/lib/src/components/text_input.dart b/mobile/packages/ui/lib/src/components/text_input.dart index 1b3fb91f51..71d3248ac1 100644 --- a/mobile/packages/ui/lib/src/components/text_input.dart +++ b/mobile/packages/ui/lib/src/components/text_input.dart @@ -1,33 +1,46 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; class ImmichTextInput extends StatefulWidget { - final String label; + final String? label; final String? hintText; final TextEditingController? controller; final FocusNode? focusNode; final String? Function(String?)? validator; - final void Function(BuildContext, String)? onSubmit; + final void Function(String value)? onSubmit; final TextInputType keyboardType; final TextInputAction? keyboardAction; final List? autofillHints; final Widget? suffixIcon; final bool obscureText; - final bool autoCorrect; + final bool autocorrect; + final SmartDashesType? smartDashesType; + final SmartQuotesType? smartQuotesType; + final List? inputFormatters; + final bool enabled; + final bool autofocus; + final AutovalidateMode? autovalidateMode; const ImmichTextInput({ super.key, this.controller, this.focusNode, - required this.label, + this.label, this.hintText, this.validator, this.onSubmit, - this.keyboardType = TextInputType.text, + this.keyboardType = .text, this.keyboardAction, this.autofillHints, this.suffixIcon, this.obscureText = false, - this.autoCorrect = true, + this.autocorrect = true, + this.smartDashesType, + this.smartQuotesType, + this.inputFormatters, + this.enabled = true, + this.autofocus = false, + this.autovalidateMode, }); @override @@ -36,7 +49,6 @@ class ImmichTextInput extends StatefulWidget { class _ImmichTextInputState extends State { late final FocusNode _focusNode; - String? _error; @override void initState() { @@ -52,40 +64,26 @@ class _ImmichTextInputState extends State { super.dispose(); } - String? _validateInput(String? value) { - setState(() { - _error = widget.validator?.call(value); - }); - return null; - } - - bool get _hasError => _error != null && _error!.isNotEmpty; - @override Widget build(BuildContext context) { - final themeData = Theme.of(context); - return TextFormField( controller: widget.controller, focusNode: _focusNode, - decoration: InputDecoration( - hintText: widget.hintText, - labelText: widget.label, - labelStyle: themeData.inputDecorationTheme.labelStyle?.copyWith( - color: _hasError ? themeData.colorScheme.error : null, - ), - errorText: _error, - suffixIcon: widget.suffixIcon, - ), + enabled: widget.enabled, + autofocus: widget.autofocus, + autovalidateMode: widget.autovalidateMode, + decoration: InputDecoration(hintText: widget.hintText, labelText: widget.label, suffixIcon: widget.suffixIcon), obscureText: widget.obscureText, - validator: _validateInput, - keyboardType: widget.keyboardType, + validator: widget.validator, textInputAction: widget.keyboardAction, - autocorrect: widget.autoCorrect, - autofillHints: widget.autofillHints, - onTap: () => setState(() => _error = null), onTapOutside: (_) => _focusNode.unfocus(), - onFieldSubmitted: (value) => widget.onSubmit?.call(context, value), + onFieldSubmitted: (value) => widget.onSubmit?.call(value), + keyboardType: widget.keyboardType, + autofillHints: widget.autofillHints, + autocorrect: widget.autocorrect, + smartDashesType: widget.smartDashesType, + smartQuotesType: widget.smartQuotesType, + inputFormatters: widget.inputFormatters, ); } } diff --git a/mobile/packages/ui/lib/src/components/url_input.dart b/mobile/packages/ui/lib/src/components/url_input.dart new file mode 100644 index 0000000000..b23136a122 --- /dev/null +++ b/mobile/packages/ui/lib/src/components/url_input.dart @@ -0,0 +1,30 @@ +import 'package:flutter/services.dart'; +import 'package:immich_ui/src/components/text_input.dart'; + +class ImmichURLInput extends ImmichTextInput { + ImmichURLInput({ + super.key, + super.controller, + super.focusNode, + super.label, + super.hintText, + super.validator, + super.onSubmit, + super.keyboardAction, + super.suffixIcon, + super.enabled, + super.autofocus, + super.autovalidateMode, + }) : super( + keyboardType: .url, + autofillHints: const [AutofillHints.url], + autocorrect: false, + smartDashesType: .disabled, + smartQuotesType: .disabled, + inputFormatters: _formatters, + ); + + static final List _formatters = List.unmodifiable([ + FilteringTextInputFormatter.deny(RegExp(r'\s')), + ]); +} diff --git a/mobile/packages/ui/lib/src/theme.dart b/mobile/packages/ui/lib/src/theme.dart index 387723b8ce..891b41ee91 100644 --- a/mobile/packages/ui/lib/src/theme.dart +++ b/mobile/packages/ui/lib/src/theme.dart @@ -15,23 +15,21 @@ class ImmichThemeProvider extends StatelessWidget { brightness: colorScheme.brightness, inputDecorationTheme: InputDecorationTheme( floatingLabelBehavior: FloatingLabelBehavior.always, - focusedBorder: OutlineInputBorder( - borderSide: BorderSide(color: colorScheme.primary), - borderRadius: const BorderRadius.all(Radius.circular(ImmichRadius.md)), - ), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide(color: colorScheme.primary), - borderRadius: const BorderRadius.all(Radius.circular(ImmichRadius.md)), - ), - errorBorder: OutlineInputBorder( - borderSide: BorderSide(color: colorScheme.error), - borderRadius: const BorderRadius.all(Radius.circular(ImmichRadius.md)), - ), - focusedErrorBorder: OutlineInputBorder( - borderSide: BorderSide(color: colorScheme.error), - borderRadius: const BorderRadius.all(Radius.circular(ImmichRadius.md)), - ), - labelStyle: TextStyle(color: colorScheme.primary, fontWeight: FontWeight.w600), + border: WidgetStateInputBorder.resolveWith((states) { + final color = states.contains(WidgetState.error) + ? colorScheme.error + : states.contains(WidgetState.focused) + ? colorScheme.primary + : colorScheme.outline; + return OutlineInputBorder( + borderSide: BorderSide(color: color), + borderRadius: const BorderRadius.all(Radius.circular(ImmichRadius.md)), + ); + }), + labelStyle: WidgetStateTextStyle.resolveWith((states) { + final color = states.contains(WidgetState.error) ? colorScheme.error : colorScheme.primary; + return TextStyle(color: color, fontWeight: FontWeight.w600); + }), hintStyle: const TextStyle(fontSize: ImmichTextSize.body), errorStyle: TextStyle(color: colorScheme.error, fontWeight: FontWeight.w600), ), diff --git a/mobile/packages/ui/pubspec.lock b/mobile/packages/ui/pubspec.lock index 4ac863d0f7..294c46e424 100644 --- a/mobile/packages/ui/pubspec.lock +++ b/mobile/packages/ui/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 url: "https://pub.dev" source: hosted - version: "2.13.0" + version: "2.13.1" boolean_selector: dependency: transitive description: @@ -103,10 +103,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.18.0" path: dependency: transitive description: @@ -124,10 +124,10 @@ packages: dependency: transitive description: name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" url: "https://pub.dev" source: hosted - version: "1.10.1" + version: "1.10.2" stack_trace: dependency: transitive description: @@ -164,10 +164,10 @@ packages: dependency: transitive description: name: test_api - sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" url: "https://pub.dev" source: hosted - version: "0.7.10" + version: "0.7.11" vector_math: dependency: transitive description: @@ -180,10 +180,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" url: "https://pub.dev" source: hosted - version: "15.0.2" + version: "15.2.0" sdks: dart: ">=3.11.0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/mobile/packages/ui/showcase/lib/pages/components/form_page.dart b/mobile/packages/ui/showcase/lib/pages/components/form_page.dart index 14567031de..f4480026b3 100644 --- a/mobile/packages/ui/showcase/lib/pages/components/form_page.dart +++ b/mobile/packages/ui/showcase/lib/pages/components/form_page.dart @@ -39,7 +39,7 @@ class _FormPageState extends State { _result = 'Form submitted!'; }); }, - child: Column( + builder: (context, form) => Column( spacing: 10, children: [ ImmichTextInput( @@ -54,6 +54,7 @@ class _FormPageState extends State { controller: _passwordController, validator: (value) => value?.isEmpty ?? true ? 'Required' : null, + onSubmit: (_) => form.submit(), ), ], ), diff --git a/mobile/packages/ui/showcase/pubspec.lock b/mobile/packages/ui/showcase/pubspec.lock index c676b23c53..d375d4b05b 100644 --- a/mobile/packages/ui/showcase/pubspec.lock +++ b/mobile/packages/ui/showcase/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 url: "https://pub.dev" source: hosted - version: "2.13.0" + version: "2.13.1" boolean_selector: dependency: transitive description: @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: ffi - sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.2.0" file: dependency: transitive description: @@ -124,10 +124,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: "5540e4a3f416dd4a93458257b908eb88353cbd0fb5b0a3d1bd7d849ba1e88735" + sha256: "92d8cee7c57dff0a6c409c05597b460002434eccf7424a712283225b3962d03f" url: "https://pub.dev" source: hosted - version: "17.2.1" + version: "17.2.3" immich_ui: dependency: "direct main" description: @@ -211,10 +211,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.18.0" path: dependency: transitive description: @@ -248,10 +248,10 @@ packages: dependency: transitive description: name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" url: "https://pub.dev" source: hosted - version: "1.10.1" + version: "1.10.2" stack_trace: dependency: transitive description: @@ -312,10 +312,10 @@ packages: dependency: transitive description: name: test_api - sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" url: "https://pub.dev" source: hosted - version: "0.7.10" + version: "0.7.11" typed_data: dependency: transitive description: @@ -328,10 +328,10 @@ packages: dependency: transitive description: name: uuid - sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 + sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" url: "https://pub.dev" source: hosted - version: "4.5.2" + version: "4.5.3" vector_math: dependency: transitive description: @@ -344,10 +344,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" url: "https://pub.dev" source: hosted - version: "15.0.2" + version: "15.2.0" web: dependency: transitive description: diff --git a/mobile/pigeon/native_sync_api.dart b/mobile/pigeon/native_sync_api.dart index 5196b19b76..c4ee3af945 100644 --- a/mobile/pigeon/native_sync_api.dart +++ b/mobile/pigeon/native_sync_api.dart @@ -11,14 +11,7 @@ import 'package:pigeon/pigeon.dart'; dartPackageName: 'immich_mobile', ), ) -enum PlatformAssetPlaybackStyle { - unknown, - image, - video, - imageAnimated, - livePhoto, - videoLooping, -} +enum PlatformAssetPlaybackStyle { unknown, image, video, imageAnimated, livePhoto, videoLooping } class PlatformAsset { final String id; @@ -146,6 +139,9 @@ abstract class NativeSyncApi { @TaskQueue(type: TaskQueueType.serialBackgroundThread) Map> getTrashedAssets(); + @async + bool restoreFromTrashById(String mediaId, int type); + @TaskQueue(type: TaskQueueType.serialBackgroundThread) List getCloudIdForAssetIds(List assetIds); } diff --git a/mobile/pigeon/network_api.dart b/mobile/pigeon/network_api.dart index 704efed770..a701995342 100644 --- a/mobile/pigeon/network_api.dart +++ b/mobile/pigeon/network_api.dart @@ -44,4 +44,6 @@ abstract class NetworkApi { int getClientPointer(); void setRequestHeaders(Map headers, List serverUrls, String? token); + + String getAppGroupId(); } diff --git a/mobile/pigeon/permission_api.dart b/mobile/pigeon/permission_api.dart new file mode 100644 index 0000000000..a924f32e27 --- /dev/null +++ b/mobile/pigeon/permission_api.dart @@ -0,0 +1,23 @@ +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon( + PigeonOptions( + dartOut: 'lib/platform/permission_api.g.dart', + swiftOut: 'ios/Runner/Permission/PermissionApi.g.swift', + swiftOptions: SwiftOptions(), + kotlinOut: 'android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApi.g.kt', + kotlinOptions: KotlinOptions(package: 'app.alextran.immich.permission'), + dartOptions: DartOptions(), + dartPackageName: 'immich_mobile', + ), +) +@HostApi() +abstract class PermissionApi { + bool hasManageMediaPermission(); + + @async + bool requestManageMediaPermission(); + + @async + bool manageMediaPermission(); +} diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 5be3ff9768..0e8eda62e9 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -133,10 +133,10 @@ packages: dependency: transitive description: name: build - sha256: aadd943f4f8cc946882c954c187e6115a84c98c81ad1d9c6cbf0895a8c85da9c + sha256: a156715e7cd728130c592f30552575908aae5b100005fbc1f0fb16b3c03a3d10 url: "https://pub.dev" source: hosted - version: "4.0.5" + version: "4.0.6" build_config: dependency: transitive description: @@ -157,10 +157,10 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "521daf8d189deb79ba474e43a696b41c49fb3987818dbacf3308f1e03673a75e" + sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6" url: "https://pub.dev" source: hosted - version: "2.13.1" + version: "2.15.0" built_collection: dependency: transitive description: @@ -173,10 +173,10 @@ packages: dependency: transitive description: name: built_value - sha256: "0730c18c770d05636a8f945c32a4d7d81cb6e0f0148c8db4ad12e7748f7e49af" + sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56" url: "https://pub.dev" source: hosted - version: "8.12.5" + version: "8.12.6" cast: dependency: "direct main" description: @@ -358,18 +358,18 @@ packages: dependency: "direct main" description: name: drift - sha256: "055c249d1f91be5a47fe447f88afc24c4ca6f4cd6c5ed66767b4797d48acc2e5" + sha256: "8033500116b24398fba0cca0369cc31678cd627c01e41753a61186911cea743e" url: "https://pub.dev" source: hosted - version: "2.32.1" + version: "2.33.0" drift_dev: dependency: "direct dev" description: name: drift_dev - sha256: "88a9de3af8571518148a6d8a513b57779fd1e60a026d3ab8a481a878fba01d91" + sha256: b3dd5b75e30522a91da8abda9f5bb17230cb038097f6d15fa75d42bb563428aa url: "https://pub.dev" source: hosted - version: "2.32.1" + version: "2.33.0" drift_flutter: dependency: "direct main" description: @@ -613,10 +613,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: "1ded017b39c8e15c8948ea855070a5ff8ff8b3d5e83f3446e02d6bb12add7ad9" + sha256: "35882981abcbfb8c15b286f0cd690ff25bac12d95eff3e25ee207f37d4c42e7f" url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.3.0" flutter_test: dependency: "direct dev" description: flutter @@ -772,10 +772,10 @@ packages: dependency: transitive description: name: hooks - sha256: e79ed1e8e1929bc6ecb6ec85f0cb519c887aa5b423705ded0d0f2d9226def388 + sha256: "025f060e86d2d4c3c47b56e33caf7f93bf9283340f26d23424ebcfccf34f621e" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.0.3" hooks_riverpod: dependency: "direct main" description: @@ -844,18 +844,18 @@ packages: dependency: "direct main" description: name: image_picker - sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320" + sha256: "91c025426c2881c551100bce834e201c835a170151545f58d17da5180ca7d9ac" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" image_picker_android: dependency: transitive description: name: image_picker_android - sha256: "66810af8e99b2657ee98e5c6f02064f69bb63f7a70e343937f70946c5f8c6622" + sha256: d5b3e1774af29c9ab00103afb0d4614070f924d2e0057ac867ec98800114793f url: "https://pub.dev" source: hosted - version: "0.8.13+16" + version: "0.8.13+17" image_picker_for_web: dependency: transitive description: @@ -952,10 +952,10 @@ packages: dependency: transitive description: name: json_annotation - sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 + sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" url: "https://pub.dev" source: hosted - version: "4.11.0" + version: "4.12.0" leak_tracker: dependency: transitive description: @@ -984,10 +984,10 @@ packages: dependency: transitive description: name: lean_builder - sha256: ee4117b03e93a4eb83e1a78c8e7a1dc22188d43bb142309982be48673a1b3a53 + sha256: c16e95ddf7b2d49dd551357b7212fe2ce9f13ec7ad1b1e660c157184031e96c0 url: "https://pub.dev" source: hosted - version: "0.1.7" + version: "0.1.10" lints: dependency: transitive description: @@ -1429,6 +1429,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.0" + record_use: + dependency: transitive + description: + name: record_use + sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" + url: "https://pub.dev" + source: hosted + version: "0.6.0" riverpod: dependency: transitive description: @@ -1607,10 +1615,10 @@ packages: dependency: transitive description: name: source_gen - sha256: "732792cfd197d2161a65bb029606a46e0a18ff30ef9e141a7a82172b05ea8ecd" + sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02 url: "https://pub.dev" source: hosted - version: "4.2.2" + version: "4.2.3" source_span: dependency: transitive description: @@ -1647,10 +1655,10 @@ packages: dependency: transitive description: name: sqlparser - sha256: ab2b467425f1d4f3acfa5fd11a08226f7d6c26ff102c06be1807e1dff34e050b + sha256: ecdc06d4a7d79dcbc928d99afd2f7f5b0f98a637c46f89be83d911617f759978 url: "https://pub.dev" source: hosted - version: "0.44.3" + version: "0.44.4" stack_trace: dependency: transitive description: @@ -1807,10 +1815,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f + sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.3" url_launcher_windows: dependency: transitive description: @@ -1831,10 +1839,10 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "81da85e9ca8885ade47f9685b953cb098970d11be4821ac765580a6607ea4373" + sha256: "2306c03da2ba81724afeb589c351ebbc0aa7d86005925be8f8735856dbe5e42d" url: "https://pub.dev" source: hosted - version: "1.1.21" + version: "1.2.2" vector_graphics_codec: dependency: transitive description: @@ -1847,10 +1855,10 @@ packages: dependency: transitive description: name: vector_graphics_compiler - sha256: "5a88dd14c0954a5398af544651c7fb51b457a2a556949bfb25369b210ef73a74" + sha256: b9b3f391857781aa96acacef96066f2f49b4cd03cf9fce3ca4d8da2ef5ea129e url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.3" vector_math: dependency: transitive description: @@ -1863,10 +1871,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "046d3928e16fa4dc46e8350415661755ab759d9fc97fc21b5ab295f71e4f0499" + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" url: "https://pub.dev" source: hosted - version: "15.1.0" + version: "15.2.0" wakelock_plus: dependency: "direct main" description: @@ -1879,10 +1887,10 @@ packages: dependency: transitive description: name: wakelock_plus_platform_interface - sha256: "14b2e5b9e35c2631e656913c47adecdd71633ae92896a27a64c8f1fcfabc21cc" + sha256: b13f99e992e7ae6a152e16c5559d3c07ff445b13330192662494e614ca3e7d7b url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "1.5.1" watcher: dependency: transitive description: diff --git a/mobile/test/domain/services/local_sync_service_test.dart b/mobile/test/domain/services/local_sync_service_test.dart index c15bf9b2d0..e0e6b663a7 100644 --- a/mobile/test/domain/services/local_sync_service_test.dart +++ b/mobile/test/domain/services/local_sync_service_test.dart @@ -10,17 +10,15 @@ import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart'; import 'package:immich_mobile/platform/native_sync_api.g.dart'; -import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; +import 'package:immich_mobile/repositories/asset_media.repository.dart'; import 'package:mocktail/mocktail.dart'; import '../../domain/service.mock.dart'; import '../../fixtures/asset.stub.dart'; import '../../infrastructure/repository.mock.dart'; -import '../../mocks/asset_entity.mock.dart'; import '../../repository.mocks.dart'; void main() { @@ -28,8 +26,8 @@ void main() { late DriftLocalAlbumRepository mockLocalAlbumRepository; late DriftLocalAssetRepository mockLocalAssetRepository; late DriftTrashedLocalAssetRepository mockTrashedLocalAssetRepository; - late LocalFilesManagerRepository mockLocalFilesManager; - late StorageRepository mockStorageRepository; + late AssetMediaRepository mockAssetMediaRepository; + late MockPermissionRepository mockPermissionRepository; late MockNativeSyncApi mockNativeSyncApi; late Drift db; @@ -51,8 +49,8 @@ void main() { mockLocalAlbumRepository = MockLocalAlbumRepository(); mockLocalAssetRepository = MockLocalAssetRepository(); mockTrashedLocalAssetRepository = MockTrashedLocalAssetRepository(); - mockLocalFilesManager = MockLocalFilesManagerRepository(); - mockStorageRepository = MockStorageRepository(); + mockAssetMediaRepository = MockAssetMediaRepository(); + mockPermissionRepository = MockPermissionRepository(); mockNativeSyncApi = MockNativeSyncApi(); when(() => mockNativeSyncApi.shouldFullSync()).thenAnswer((_) async => false); @@ -65,25 +63,28 @@ void main() { when(() => mockTrashedLocalAssetRepository.getToTrash()).thenAnswer((_) async => {}); when(() => mockTrashedLocalAssetRepository.applyRestoredAssets(any())).thenAnswer((_) async {}); when(() => mockTrashedLocalAssetRepository.trashLocalAsset(any())).thenAnswer((_) async {}); - when(() => mockLocalFilesManager.moveToTrash(any>())).thenAnswer((_) async => true); + when(() => mockAssetMediaRepository.deleteAll(any())).thenAnswer((invocation) async { + final ids = invocation.positionalArguments.first as List; + return ids; + }); sut = LocalSyncService( localAlbumRepository: mockLocalAlbumRepository, localAssetRepository: mockLocalAssetRepository, trashedLocalAssetRepository: mockTrashedLocalAssetRepository, - localFilesManager: mockLocalFilesManager, - storageRepository: mockStorageRepository, + assetMediaRepository: mockAssetMediaRepository, + permissionRepository: mockPermissionRepository, nativeSyncApi: mockNativeSyncApi, ); await Store.put(StoreKey.manageLocalMediaAndroid, false); - when(() => mockLocalFilesManager.hasManageMediaPermission()).thenAnswer((_) async => false); + when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => false); }); group('LocalSyncService - syncTrashedAssets gating', () { test('invokes syncTrashedAssets when Android flag enabled and permission granted', () async { await Store.put(StoreKey.manageLocalMediaAndroid, true); - when(() => mockLocalFilesManager.hasManageMediaPermission()).thenAnswer((_) async => true); + when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => true); await sut.sync(); @@ -93,7 +94,7 @@ void main() { test('skips syncTrashedAssets when store flag disabled', () async { await Store.put(StoreKey.manageLocalMediaAndroid, false); - when(() => mockLocalFilesManager.hasManageMediaPermission()).thenAnswer((_) async => true); + when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => true); await sut.sync(); @@ -102,7 +103,7 @@ void main() { test('skips syncTrashedAssets when MANAGE_MEDIA permission absent', () async { await Store.put(StoreKey.manageLocalMediaAndroid, true); - when(() => mockLocalFilesManager.hasManageMediaPermission()).thenAnswer((_) async => false); + when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => false); await sut.sync(); @@ -114,7 +115,7 @@ void main() { addTearDown(() => debugDefaultTargetPlatformOverride = TargetPlatform.android); await Store.put(StoreKey.manageLocalMediaAndroid, true); - when(() => mockLocalFilesManager.hasManageMediaPermission()).thenAnswer((_) async => true); + when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => true); await sut.sync(); @@ -131,13 +132,13 @@ void main() { durationMs: 0, orientation: 0, isFavorite: false, - playbackStyle: PlatformAssetPlaybackStyle.image + playbackStyle: PlatformAssetPlaybackStyle.image, ); final assetsToRestore = [LocalAssetStub.image1]; when(() => mockTrashedLocalAssetRepository.getToRestore()).thenAnswer((_) async => assetsToRestore); final restoredIds = ['image1']; - when(() => mockLocalFilesManager.restoreAssetsFromTrash(any())).thenAnswer((invocation) async { + when(() => mockAssetMediaRepository.restoreAssetsFromTrash(any())).thenAnswer((invocation) async { final Iterable requested = invocation.positionalArguments.first as Iterable; expect(requested, orderedEquals(assetsToRestore)); return restoredIds; @@ -150,10 +151,6 @@ void main() { }, ); - final assetEntity = MockAssetEntity(); - when(() => assetEntity.getMediaUrl()).thenAnswer((_) async => 'content://local-trash'); - when(() => mockStorageRepository.getAssetEntityForAsset(localAssetToTrash)).thenAnswer((_) async => assetEntity); - await sut.processTrashedAssets({ 'album-a': [platformAsset], }); @@ -168,12 +165,11 @@ void main() { expect(trashedEntry.asset.name, platformAsset.name); verify(() => mockTrashedLocalAssetRepository.getToTrash()).called(1); - verify(() => mockLocalFilesManager.restoreAssetsFromTrash(any())).called(1); + verify(() => mockAssetMediaRepository.restoreAssetsFromTrash(any())).called(1); verify(() => mockTrashedLocalAssetRepository.applyRestoredAssets(restoredIds)).called(1); - verify(() => mockStorageRepository.getAssetEntityForAsset(localAssetToTrash)).called(1); - final moveArgs = verify(() => mockLocalFilesManager.moveToTrash(captureAny())).captured.single as List; - expect(moveArgs, ['content://local-trash']); + final moveArgs = verify(() => mockAssetMediaRepository.deleteAll(captureAny())).captured.single as List; + expect(moveArgs, ['local-trash']); final trashArgs = verify(() => mockTrashedLocalAssetRepository.trashLocalAsset(captureAny())).captured.single as Map>; @@ -181,6 +177,26 @@ void main() { expect(trashArgs['album-a'], [localAssetToTrash]); }); + test('records only local assets that were moved to device trash', () async { + final movedAsset = LocalAssetStub.image1.copyWith(id: 'moved-local', checksum: 'checksum-moved'); + final skippedAsset = LocalAssetStub.image2.copyWith(id: 'skipped-local', checksum: 'checksum-skipped'); + when(() => mockTrashedLocalAssetRepository.getToTrash()).thenAnswer( + (_) async => { + 'album-a': [movedAsset], + 'album-b': [skippedAsset], + }, + ); + when(() => mockAssetMediaRepository.deleteAll(any())).thenAnswer((_) async => ['moved-local']); + + await sut.processTrashedAssets({}); + + final trashArgs = + verify(() => mockTrashedLocalAssetRepository.trashLocalAsset(captureAny())).captured.single + as Map>; + expect(trashArgs.keys, ['album-a']); + expect(trashArgs['album-a'], [movedAsset]); + }); + test('does not attempt restore when repository has no assets to restore', () async { when(() => mockTrashedLocalAssetRepository.getToRestore()).thenAnswer((_) async => []); @@ -190,7 +206,7 @@ void main() { verify(() => mockTrashedLocalAssetRepository.processTrashSnapshot(captureAny())).captured.single as Iterable; expect(trashedSnapshot, isEmpty); - verifyNever(() => mockLocalFilesManager.restoreAssetsFromTrash(any())); + verifyNever(() => mockAssetMediaRepository.restoreAssetsFromTrash(any())); verifyNever(() => mockTrashedLocalAssetRepository.applyRestoredAssets(any())); }); @@ -199,7 +215,7 @@ void main() { await sut.processTrashedAssets({}); - verifyNever(() => mockLocalFilesManager.moveToTrash(any())); + verifyNever(() => mockAssetMediaRepository.deleteAll(any())); verifyNever(() => mockTrashedLocalAssetRepository.trashLocalAsset(any())); }); }); @@ -215,7 +231,7 @@ void main() { isFavorite: false, createdAt: 1700000000, updatedAt: 1732000000, - playbackStyle: PlatformAssetPlaybackStyle.image + playbackStyle: PlatformAssetPlaybackStyle.image, ); final localAsset = platformAsset.toLocalAsset(); diff --git a/mobile/test/domain/services/store_service_test.dart b/mobile/test/domain/services/store_service_test.dart index 0a55f8d5c7..bb439b3d72 100644 --- a/mobile/test/domain/services/store_service_test.dart +++ b/mobile/test/domain/services/store_service_test.dart @@ -9,7 +9,7 @@ import 'package:mocktail/mocktail.dart'; import '../../infrastructure/repository.mock.dart'; const _kAccessToken = '#ThisIsAToken'; -const _kEnableBackup = false; +const _kAdvancedTroubleshooting = false; const _kVersion = 2; void main() { @@ -22,13 +22,13 @@ void main() { mockDriftStoreRepo = MockDriftStoreRepository(); // For generics, we need to provide fallback to each concrete type to avoid runtime errors registerFallbackValue(StoreKey.accessToken); - registerFallbackValue(StoreKey.backupTriggerDelay); - registerFallbackValue(StoreKey.enableBackup); + registerFallbackValue(StoreKey.version); + registerFallbackValue(StoreKey.advancedTroubleshooting); when(() => mockDriftStoreRepo.getAll()).thenAnswer( (_) async => [ const StoreDto(StoreKey.accessToken, _kAccessToken), - const StoreDto(StoreKey.enableBackup, _kEnableBackup), + const StoreDto(StoreKey.advancedTroubleshooting, _kAdvancedTroubleshooting), const StoreDto(StoreKey.version, _kVersion), ], ); @@ -46,7 +46,7 @@ void main() { test('Populates the internal cache on init', () { verify(() => mockDriftStoreRepo.getAll()).called(1); expect(sut.tryGet(StoreKey.accessToken), _kAccessToken); - expect(sut.tryGet(StoreKey.enableBackup), _kEnableBackup); + expect(sut.tryGet(StoreKey.advancedTroubleshooting), _kAdvancedTroubleshooting); expect(sut.tryGet(StoreKey.version), _kVersion); // Other keys should be null expect(sut.tryGet(StoreKey.currentUser), isNull); @@ -147,7 +147,7 @@ void main() { await sut.clear(); verify(() => mockDriftStoreRepo.deleteAll()).called(1); expect(sut.tryGet(StoreKey.accessToken), isNull); - expect(sut.tryGet(StoreKey.enableBackup), isNull); + expect(sut.tryGet(StoreKey.advancedTroubleshooting), isNull); expect(sut.tryGet(StoreKey.version), isNull); }); }); diff --git a/mobile/test/domain/services/sync_stream_service_test.dart b/mobile/test/domain/services/sync_stream_service_test.dart index 1bee1dccde..ef29997e0b 100644 --- a/mobile/test/domain/services/sync_stream_service_test.dart +++ b/mobile/test/domain/services/sync_stream_service_test.dart @@ -12,12 +12,11 @@ import 'package:immich_mobile/domain/services/sync_stream.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart'; -import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; +import 'package:immich_mobile/repositories/asset_media.repository.dart'; import 'package:immich_mobile/utils/semver.dart'; import 'package:mocktail/mocktail.dart'; import 'package:openapi/api.dart'; @@ -26,7 +25,6 @@ import '../../api.mocks.dart'; import '../../fixtures/asset.stub.dart'; import '../../fixtures/sync_stream.stub.dart'; import '../../infrastructure/repository.mock.dart'; -import '../../mocks/asset_entity.mock.dart'; import '../../repository.mocks.dart'; import '../../service.mocks.dart'; @@ -52,8 +50,8 @@ void main() { late SyncApiRepository mockSyncApiRepo; late DriftLocalAssetRepository mockLocalAssetRepo; late DriftTrashedLocalAssetRepository mockTrashedLocalAssetRepo; - late LocalFilesManagerRepository mockLocalFilesManagerRepo; - late StorageRepository mockStorageRepo; + late AssetMediaRepository mockAssetMediaRepo; + late MockPermissionRepository mockPermissionRepo; late MockApiService mockApi; late MockServerApi mockServerApi; late MockSyncMigrationRepository mockSyncMigrationRepo; @@ -86,8 +84,8 @@ void main() { mockSyncApiRepo = MockSyncApiRepository(); mockLocalAssetRepo = MockLocalAssetRepository(); mockTrashedLocalAssetRepo = MockTrashedLocalAssetRepository(); - mockLocalFilesManagerRepo = MockLocalFilesManagerRepository(); - mockStorageRepo = MockStorageRepository(); + mockAssetMediaRepo = MockAssetMediaRepository(); + mockPermissionRepo = MockPermissionRepository(); mockAbortCallbackWrapper = _MockAbortCallbackWrapper(); mockResetCallbackWrapper = _MockAbortCallbackWrapper(); mockApi = MockApiService(); @@ -159,8 +157,8 @@ void main() { syncStreamRepository: mockSyncStreamRepo, localAssetRepository: mockLocalAssetRepo, trashedLocalAssetRepository: mockTrashedLocalAssetRepo, - localFilesManager: mockLocalFilesManagerRepo, - storageRepository: mockStorageRepo, + assetMediaRepository: mockAssetMediaRepo, + permissionRepository: mockPermissionRepo, api: mockApi, syncMigrationRepository: mockSyncMigrationRepo, ); @@ -170,10 +168,12 @@ void main() { when(() => mockTrashedLocalAssetRepo.getToRestore()).thenAnswer((_) async => []); when(() => mockTrashedLocalAssetRepo.applyRestoredAssets(any())).thenAnswer((_) async {}); hasManageMediaPermission = false; - when(() => mockLocalFilesManagerRepo.hasManageMediaPermission()).thenAnswer((_) async => hasManageMediaPermission); - when(() => mockLocalFilesManagerRepo.moveToTrash(any())).thenAnswer((_) async => true); - when(() => mockLocalFilesManagerRepo.restoreAssetsFromTrash(any())).thenAnswer((_) async => []); - when(() => mockStorageRepo.getAssetEntityForAsset(any())).thenAnswer((_) async => null); + when(() => mockPermissionRepo.hasManageMediaPermission()).thenAnswer((_) async => hasManageMediaPermission); + when(() => mockAssetMediaRepo.deleteAll(any())).thenAnswer((invocation) async { + final ids = invocation.positionalArguments.first as List; + return ids; + }); + when(() => mockAssetMediaRepo.restoreAssetsFromTrash(any())).thenAnswer((_) async => []); await Store.put(StoreKey.manageLocalMediaAndroid, false); }); @@ -241,8 +241,8 @@ void main() { syncStreamRepository: mockSyncStreamRepo, localAssetRepository: mockLocalAssetRepo, trashedLocalAssetRepository: mockTrashedLocalAssetRepo, - localFilesManager: mockLocalFilesManagerRepo, - storageRepository: mockStorageRepo, + assetMediaRepository: mockAssetMediaRepo, + permissionRepository: mockPermissionRepo, cancelChecker: cancellationChecker.call, api: mockApi, syncMigrationRepository: mockSyncMigrationRepo, @@ -282,8 +282,8 @@ void main() { syncStreamRepository: mockSyncStreamRepo, localAssetRepository: mockLocalAssetRepo, trashedLocalAssetRepository: mockTrashedLocalAssetRepo, - localFilesManager: mockLocalFilesManagerRepo, - storageRepository: mockStorageRepo, + assetMediaRepository: mockAssetMediaRepo, + permissionRepository: mockPermissionRepo, cancelChecker: cancellationChecker.call, api: mockApi, syncMigrationRepository: mockSyncMigrationRepo, @@ -424,18 +424,10 @@ void main() { return assetsByAlbum; }); - final localEntity = MockAssetEntity(); - when(() => localEntity.getMediaUrl()).thenAnswer((_) async => 'content://local-only'); - when(() => mockStorageRepo.getAssetEntityForAsset(localAsset)).thenAnswer((_) async => localEntity); - - final mergedEntity = MockAssetEntity(); - when(() => mergedEntity.getMediaUrl()).thenAnswer((_) async => 'content://merged-local'); - when(() => mockStorageRepo.getAssetEntityForAsset(mergedAsset)).thenAnswer((_) async => mergedEntity); - - when(() => mockLocalFilesManagerRepo.moveToTrash(any())).thenAnswer((invocation) async { - final urls = invocation.positionalArguments.first as List; - expect(urls, unorderedEquals(['content://local-only', 'content://merged-local'])); - return true; + when(() => mockAssetMediaRepo.deleteAll(any())).thenAnswer((invocation) async { + final ids = invocation.positionalArguments.first as List; + expect(ids, unorderedEquals(['local-only', 'merged-local'])); + return ids; }); final events = [ @@ -461,10 +453,51 @@ void main() { await simulateEvents(events); - verify(() => mockTrashedLocalAssetRepo.trashLocalAsset(assetsByAlbum)).called(1); + final trashArgs = + verify(() => mockTrashedLocalAssetRepo.trashLocalAsset(captureAny())).captured.single + as Map>; + expect(trashArgs.keys, unorderedEquals(['album-a', 'album-b'])); + expect(trashArgs['album-a'], [localAsset]); + expect(trashArgs['album-b'], [mergedAsset]); + verify(() => mockAssetMediaRepo.deleteAll(any())).called(1); verify(() => mockSyncApiRepo.ack(['asset-remote-only-3'])).called(1); }); + test("records only assets that were moved to device trash", () async { + final movedAsset = LocalAssetStub.image1.copyWith(id: 'moved-local', checksum: 'checksum-moved'); + final skippedAsset = LocalAssetStub.image2.copyWith(id: 'skipped-local', checksum: 'checksum-skipped'); + when(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).thenAnswer( + (_) async => { + 'album-a': [movedAsset], + 'album-b': [skippedAsset], + }, + ); + when(() => mockAssetMediaRepo.deleteAll(any())).thenAnswer((_) async => ['moved-local']); + + final events = [ + SyncStreamStub.assetTrashed( + id: 'remote-moved', + checksum: movedAsset.checksum!, + ack: 'asset-remote-moved', + trashedAt: DateTime(2025, 5, 1), + ), + SyncStreamStub.assetTrashed( + id: 'remote-skipped', + checksum: skippedAsset.checksum!, + ack: 'asset-remote-skipped', + trashedAt: DateTime(2025, 5, 2), + ), + ]; + + await simulateEvents(events); + + final trashArgs = + verify(() => mockTrashedLocalAssetRepo.trashLocalAsset(captureAny())).captured.single + as Map>; + expect(trashArgs.keys, ['album-a']); + expect(trashArgs['album-a'], [movedAsset]); + }); + test("skips device trashing when no local assets match the remote trash payload", () async { final events = [ SyncStreamStub.assetTrashed( @@ -478,7 +511,7 @@ void main() { await simulateEvents(events); verify(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).called(1); - verifyNever(() => mockLocalFilesManagerRepo.moveToTrash(any())); + verifyNever(() => mockAssetMediaRepo.deleteAll(any())); verifyNever(() => mockTrashedLocalAssetRepo.trashLocalAsset(any())); }); @@ -494,7 +527,7 @@ void main() { await simulateEvents(events); verify(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).called(1); - verifyNever(() => mockLocalFilesManagerRepo.moveToTrash(any())); + verifyNever(() => mockAssetMediaRepo.deleteAll(any())); verify(() => mockSyncStreamRepo.deleteAssetsV1(any())).called(1); }); @@ -505,7 +538,7 @@ void main() { when(() => mockTrashedLocalAssetRepo.getToRestore()).thenAnswer((_) async => trashedAssets); final restoredIds = ['trashed-1']; - when(() => mockLocalFilesManagerRepo.restoreAssetsFromTrash(any())).thenAnswer((invocation) async { + when(() => mockAssetMediaRepo.restoreAssetsFromTrash(any())).thenAnswer((invocation) async { final Iterable requestedAssets = invocation.positionalArguments.first as Iterable; expect(requestedAssets, orderedEquals(trashedAssets)); return restoredIds; diff --git a/mobile/test/infrastructure/repositories/store_repository_test.dart b/mobile/test/infrastructure/repositories/store_repository_test.dart index 672776b226..3e160c29ca 100644 --- a/mobile/test/infrastructure/repositories/store_repository_test.dart +++ b/mobile/test/infrastructure/repositories/store_repository_test.dart @@ -13,7 +13,7 @@ import '../../fixtures/user.stub.dart'; const _kTestAccessToken = "#TestToken"; const _kTestVersion = 10; -const _kTestBackupRequireCharging = false; +const _kTestAdvancedTroubleshooting = false; final _kTestUser = UserStub.admin; Future _populateStore(Drift db) async { @@ -21,8 +21,8 @@ Future _populateStore(Drift db) async { batch.insert( db.storeEntity, StoreEntityCompanion( - id: Value(StoreKey.backupRequireCharging.id), - intValue: const Value(_kTestBackupRequireCharging ? 1 : 0), + id: Value(StoreKey.advancedTroubleshooting.id), + intValue: const Value(_kTestAdvancedTroubleshooting ? 1 : 0), stringValue: const Value(null), ), ); @@ -76,11 +76,11 @@ void main() { }); test('converts bool', () async { - bool? backupRequireCharging = await sut.tryGet(StoreKey.backupRequireCharging); - expect(backupRequireCharging, isNull); - await sut.upsert(StoreKey.backupRequireCharging, _kTestBackupRequireCharging); - backupRequireCharging = await sut.tryGet(StoreKey.backupRequireCharging); - expect(backupRequireCharging, _kTestBackupRequireCharging); + bool? advancedTroubleshooting = await sut.tryGet(StoreKey.advancedTroubleshooting); + expect(advancedTroubleshooting, isNull); + await sut.upsert(StoreKey.advancedTroubleshooting, _kTestAdvancedTroubleshooting); + advancedTroubleshooting = await sut.tryGet(StoreKey.advancedTroubleshooting); + expect(advancedTroubleshooting, _kTestAdvancedTroubleshooting); }); test('converts user', () async { @@ -98,11 +98,11 @@ void main() { }); test('delete()', () async { - bool? backupRequireCharging = await sut.tryGet(StoreKey.backupRequireCharging); - expect(backupRequireCharging, isFalse); - await sut.delete(StoreKey.backupRequireCharging); - backupRequireCharging = await sut.tryGet(StoreKey.backupRequireCharging); - expect(backupRequireCharging, isNull); + bool? advancedTroubleshooting = await sut.tryGet(StoreKey.advancedTroubleshooting); + expect(advancedTroubleshooting, isFalse); + await sut.delete(StoreKey.advancedTroubleshooting); + advancedTroubleshooting = await sut.tryGet(StoreKey.advancedTroubleshooting); + expect(advancedTroubleshooting, isNull); }); test('deleteAll()', () async { @@ -147,13 +147,13 @@ void main() { emitsInOrder([ [ const StoreDto(StoreKey.version, _kTestVersion), - const StoreDto(StoreKey.backupRequireCharging, _kTestBackupRequireCharging), const StoreDto(StoreKey.accessToken, _kTestAccessToken), + const StoreDto(StoreKey.advancedTroubleshooting, _kTestAdvancedTroubleshooting), ], [ const StoreDto(StoreKey.version, _kTestVersion + 10), - const StoreDto(StoreKey.backupRequireCharging, _kTestBackupRequireCharging), const StoreDto(StoreKey.accessToken, _kTestAccessToken), + const StoreDto(StoreKey.advancedTroubleshooting, _kTestAdvancedTroubleshooting), ], ]), ), diff --git a/mobile/test/medium/repositories/remote_album_repository_test.dart b/mobile/test/medium/repositories/remote_album_repository_test.dart index 5e923ea09b..e4d803a51c 100644 --- a/mobile/test/medium/repositories/remote_album_repository_test.dart +++ b/mobile/test/medium/repositories/remote_album_repository_test.dart @@ -17,6 +17,33 @@ void main() { await ctx.dispose(); }); + group('addAssets', () { + test('sets the first added asset as thumbnail when the album has no thumbnail', () async { + final user = await ctx.newUser(); + final album = await ctx.newRemoteAlbum(ownerId: user.id); + final asset = await ctx.newRemoteAsset(ownerId: user.id); + + await sut.addAssets(album.id, [asset.id]); + + final updated = await sut.get(album.id); + expect(updated?.thumbnailAssetId, asset.id); + expect(updated?.assetCount, 1); + }); + + test('preserves an existing thumbnail when adding assets', () async { + final user = await ctx.newUser(); + final thumbnail = await ctx.newRemoteAsset(ownerId: user.id); + final album = await ctx.newRemoteAlbum(ownerId: user.id, thumbnailAssetId: thumbnail.id); + final asset = await ctx.newRemoteAsset(ownerId: user.id); + + await sut.addAssets(album.id, [asset.id]); + + final updated = await sut.get(album.id); + expect(updated?.thumbnailAssetId, thumbnail.id); + expect(updated?.assetCount, 1); + }); + }); + group('getSortedAlbumIds', () { late String userId; diff --git a/mobile/test/repository.mocks.dart b/mobile/test/repository.mocks.dart index d049626f1d..80786420fd 100644 --- a/mobile/test/repository.mocks.dart +++ b/mobile/test/repository.mocks.dart @@ -2,15 +2,18 @@ import 'package:immich_mobile/repositories/asset_api.repository.dart'; import 'package:immich_mobile/repositories/asset_media.repository.dart'; import 'package:immich_mobile/repositories/auth.repository.dart'; import 'package:immich_mobile/repositories/auth_api.repository.dart'; -import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; +import 'package:immich_mobile/domain/services/tag.service.dart'; +import 'package:immich_mobile/repositories/permission.repository.dart'; import 'package:mocktail/mocktail.dart'; class MockAssetApiRepository extends Mock implements AssetApiRepository {} class MockAssetMediaRepository extends Mock implements AssetMediaRepository {} +class MockPermissionRepository extends Mock implements IPermissionRepository {} + class MockAuthApiRepository extends Mock implements AuthApiRepository {} class MockAuthRepository extends Mock implements AuthRepository {} -class MockLocalFilesManagerRepository extends Mock implements LocalFilesManagerRepository {} +class MockTagService extends Mock implements TagService {} diff --git a/mobile/test/services/action.service_test.dart b/mobile/test/services/action.service_test.dart index 87263c9ae7..f08a247a3e 100644 --- a/mobile/test/services/action.service_test.dart +++ b/mobile/test/services/action.service_test.dart @@ -27,6 +27,7 @@ void main() { late MockTrashedLocalAssetRepository trashedLocalAssetRepository; late MockAssetMediaRepository assetMediaRepository; late MockDownloadRepository downloadRepository; + late MockTagService tagService; late Drift db; @@ -53,6 +54,7 @@ void main() { trashedLocalAssetRepository = MockTrashedLocalAssetRepository(); assetMediaRepository = MockAssetMediaRepository(); downloadRepository = MockDownloadRepository(); + tagService = MockTagService(); sut = ActionService( assetApiRepository, @@ -63,6 +65,7 @@ void main() { trashedLocalAssetRepository, assetMediaRepository, downloadRepository, + tagService, ); }); diff --git a/mobile/test/services/auth.service_test.dart b/mobile/test/services/auth.service_test.dart index 8bd813e8f6..584ea57027 100644 --- a/mobile/test/services/auth.service_test.dart +++ b/mobile/test/services/auth.service_test.dart @@ -21,7 +21,6 @@ void main() { late MockApiService apiService; late MockNetworkService networkService; late MockBackgroundSyncManager backgroundSyncManager; - late MockAppSettingService appSettingsService; late Drift db; setUp(() async { @@ -30,15 +29,12 @@ void main() { apiService = MockApiService(); networkService = MockNetworkService(); backgroundSyncManager = MockBackgroundSyncManager(); - appSettingsService = MockAppSettingService(); - sut = AuthService( authApiRepository, authRepository, apiService, networkService, backgroundSyncManager, - appSettingsService, ); registerFallbackValue(Uri()); diff --git a/mobile/test/services/background_upload.service_test.dart b/mobile/test/services/background_upload.service_test.dart index 585ffcb499..dd19f2b1cc 100644 --- a/mobile/test/services/background_upload.service_test.dart +++ b/mobile/test/services/background_upload.service_test.dart @@ -11,12 +11,11 @@ import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/background_upload.service.dart'; import 'package:mocktail/mocktail.dart'; -import '../domain/service.mock.dart'; import '../fixtures/asset.stub.dart'; import '../infrastructure/repository.mock.dart'; import '../mocks/asset_entity.mock.dart'; @@ -28,13 +27,10 @@ void main() { late MockStorageRepository mockStorageRepository; late MockDriftLocalAssetRepository mockLocalAssetRepository; late MockDriftBackupRepository mockBackupRepository; - late MockAppSettingsService mockAppSettingsService; late MockAssetMediaRepository mockAssetMediaRepository; late Drift db; setUpAll(() async { - registerFallbackValue(AppSettingsEnum.useCellularForUploadPhotos); - TestWidgetsFlutterBinding.ensureInitialized(); TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( const MethodChannel('plugins.flutter.io/path_provider'), @@ -42,6 +38,7 @@ void main() { ); db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)); await StoreService.init(storeRepository: DriftStoreRepository(db)); + await MetadataRepository.ensureInitialized(db); await Store.put(StoreKey.serverEndpoint, 'http://test-server.com'); await Store.put(StoreKey.deviceId, 'test-device-id'); @@ -52,18 +49,13 @@ void main() { mockStorageRepository = MockStorageRepository(); mockLocalAssetRepository = MockDriftLocalAssetRepository(); mockBackupRepository = MockDriftBackupRepository(); - mockAppSettingsService = MockAppSettingsService(); mockAssetMediaRepository = MockAssetMediaRepository(); - when(() => mockAppSettingsService.getSetting(AppSettingsEnum.useCellularForUploadVideos)).thenReturn(false); - when(() => mockAppSettingsService.getSetting(AppSettingsEnum.useCellularForUploadPhotos)).thenReturn(false); - sut = BackgroundUploadService( mockUploadRepository, mockStorageRepository, mockLocalAssetRepository, mockBackupRepository, - mockAppSettingsService, mockAssetMediaRepository, ); @@ -179,7 +171,6 @@ void main() { mockStorageRepository, mockLocalAssetRepository, mockBackupRepository, - mockAppSettingsService, mockAssetMediaRepository, ); addTearDown(() => sutWithV24.dispose()); @@ -230,7 +221,6 @@ void main() { mockStorageRepository, mockLocalAssetRepository, mockBackupRepository, - mockAppSettingsService, mockAssetMediaRepository, ); addTearDown(() => sutAndroid.dispose()); @@ -271,7 +261,6 @@ void main() { mockStorageRepository, mockLocalAssetRepository, mockBackupRepository, - mockAppSettingsService, mockAssetMediaRepository, ); addTearDown(() => sutWithV24.dispose()); @@ -312,7 +301,6 @@ void main() { mockStorageRepository, mockLocalAssetRepository, mockBackupRepository, - mockAppSettingsService, mockAssetMediaRepository, ); addTearDown(() => sutWithV24.dispose()); diff --git a/mobile/test/unit/repositories/metadata_repository_test.dart b/mobile/test/unit/repositories/metadata_repository_test.dart index 4c29ce3a01..75b34da7cb 100644 --- a/mobile/test/unit/repositories/metadata_repository_test.dart +++ b/mobile/test/unit/repositories/metadata_repository_test.dart @@ -13,6 +13,10 @@ void main() { test('decode falls back to the default value when the raw input is unparseable', () { for (final key in MetadataKey.values) { + // String keys can decode any string. So skip them + if (key.defaultValue is String) { + continue; + } expect( key.decode('not a valid encoding for any key'), key.defaultValue, diff --git a/mobile/test/utils_legacy/action_button_utils_test.dart b/mobile/test/utils_legacy/action_button_utils_test.dart index 8bd078a433..0a6020762a 100644 --- a/mobile/test/utils_legacy/action_button_utils_test.dart +++ b/mobile/test/utils_legacy/action_button_utils_test.dart @@ -38,6 +38,7 @@ RemoteAsset createRemoteAsset({ DateTime? updatedAt, DateTime? uploadedAt, bool isFavorite = false, + DateTime? deletedAt, }) { return RemoteAsset( id: 'remote-id', @@ -51,6 +52,7 @@ RemoteAsset createRemoteAsset({ uploadedAt: uploadedAt ?? DateTime.now(), isFavorite: isFavorite, isEdited: false, + deletedAt: deletedAt, ); } @@ -459,6 +461,24 @@ void main() { expect(ActionButtonType.trash.shouldShow(context), isFalse); }); + + test('should not show when asset is already trashed', () { + final remoteAsset = createRemoteAsset(deletedAt: DateTime(2024)); + final context = ActionButtonContext( + asset: remoteAsset, + isOwner: true, + isArchived: false, + isTrashEnabled: true, + isInLockedView: false, + currentAlbum: null, + advancedTroubleshooting: false, + isStacked: false, + source: ActionSource.viewer, + timelineOrigin: TimelineOrigin.trash, + ); + + expect(ActionButtonType.trash.shouldShow(context), isFalse); + }); }); group('restoreTrash button', () { @@ -533,6 +553,24 @@ void main() { expect(ActionButtonType.deletePermanent.shouldShow(context), isFalse); }); + + test('should show when asset is trashed even with trash enabled', () { + final remoteAsset = createRemoteAsset(deletedAt: DateTime(2024)); + final context = ActionButtonContext( + asset: remoteAsset, + isOwner: true, + isArchived: false, + isTrashEnabled: true, + isInLockedView: false, + currentAlbum: null, + advancedTroubleshooting: false, + isStacked: false, + source: ActionSource.viewer, + timelineOrigin: TimelineOrigin.trash, + ); + + expect(ActionButtonType.deletePermanent.shouldShow(context), isTrue); + }); }); group('delete button', () { diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 9c2b3f32a6..badf9ce25d 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -11,9 +11,6 @@ "required": true, "in": "query", "description": "Album ID", - "x-nestjs_zod-parent-metadata": { - "description": "Activity search" - }, "schema": { "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", @@ -25,9 +22,6 @@ "required": false, "in": "query", "description": "Asset ID (if activity is for an asset)", - "x-nestjs_zod-parent-metadata": { - "description": "Activity search" - }, "schema": { "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", @@ -38,9 +32,6 @@ "name": "level", "required": false, "in": "query", - "x-nestjs_zod-parent-metadata": { - "description": "Activity search" - }, "schema": { "$ref": "#/components/schemas/ReactionLevel" } @@ -49,9 +40,6 @@ "name": "type", "required": false, "in": "query", - "x-nestjs_zod-parent-metadata": { - "description": "Activity search" - }, "schema": { "$ref": "#/components/schemas/ReactionType" } @@ -61,9 +49,6 @@ "required": false, "in": "query", "description": "Filter by user ID", - "x-nestjs_zod-parent-metadata": { - "description": "Activity search" - }, "schema": { "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", @@ -187,9 +172,6 @@ "required": true, "in": "query", "description": "Album ID", - "x-nestjs_zod-parent-metadata": { - "description": "Activity" - }, "schema": { "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", @@ -201,9 +183,6 @@ "required": false, "in": "query", "description": "Asset ID (if activity is for an asset)", - "x-nestjs_zod-parent-metadata": { - "description": "Activity" - }, "schema": { "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", @@ -8620,8 +8599,61 @@ "/plugins": { "get": { "description": "Retrieve a list of plugins available to the authenticated user.", - "operationId": "getPlugins", - "parameters": [], + "operationId": "searchPlugins", + "parameters": [ + { + "name": "description", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "enabled", + "required": false, + "in": "query", + "description": "Whether the plugin is enabled", + "schema": { + "type": "boolean" + } + }, + { + "name": "id", + "required": false, + "in": "query", + "description": "Plugin ID", + "schema": { + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", + "type": "string" + } + }, + { + "name": "name", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "title", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "version", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -8654,30 +8686,106 @@ ], "x-immich-history": [ { - "version": "v2.3.0", + "version": "v3.0.0", "state": "Added" - }, - { - "version": "v2.3.0", - "state": "Alpha" } ], - "x-immich-permission": "plugin.read", - "x-immich-state": "Alpha" + "x-immich-permission": "plugin.read" } }, - "/plugins/triggers": { + "/plugins/methods": { "get": { - "description": "Retrieve a list of all available plugin triggers.", - "operationId": "getPluginTriggers", - "parameters": [], + "description": "Retrieve a list of plugin methods", + "operationId": "searchPluginMethods", + "parameters": [ + { + "name": "description", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "enabled", + "required": false, + "in": "query", + "description": "Whether the plugin method is enabled", + "schema": { + "type": "boolean" + } + }, + { + "name": "id", + "required": false, + "in": "query", + "description": "Plugin method ID", + "schema": { + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", + "type": "string" + } + }, + { + "name": "name", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "pluginName", + "required": false, + "in": "query", + "description": "Plugin name", + "schema": { + "type": "string" + } + }, + { + "name": "pluginVersion", + "required": false, + "in": "query", + "description": "Plugin version", + "schema": { + "type": "string" + } + }, + { + "name": "title", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "trigger", + "required": false, + "in": "query", + "description": "Workflow trigger", + "schema": { + "$ref": "#/components/schemas/WorkflowTrigger" + } + }, + { + "name": "type", + "required": false, + "in": "query", + "description": "Workflow types", + "schema": { + "$ref": "#/components/schemas/WorkflowType" + } + } + ], "responses": { "200": { "content": { "application/json": { "schema": { "items": { - "$ref": "#/components/schemas/PluginTriggerResponseDto" + "$ref": "#/components/schemas/PluginMethodResponseDto" }, "type": "array" } @@ -8697,22 +8805,17 @@ "api_key": [] } ], - "summary": "List all plugin triggers", + "summary": "Retrieve plugin methods", "tags": [ "Plugins" ], "x-immich-history": [ { - "version": "v2.3.0", + "version": "v3.0.0", "state": "Added" - }, - { - "version": "v2.3.0", - "state": "Alpha" } ], - "x-immich-permission": "plugin.read", - "x-immich-state": "Alpha" + "x-immich-permission": "plugin.read" } }, "/plugins/{id}": { @@ -8760,16 +8863,11 @@ ], "x-immich-history": [ { - "version": "v2.3.0", + "version": "v3.0.0", "state": "Added" - }, - { - "version": "v2.3.0", - "state": "Alpha" } ], - "x-immich-permission": "plugin.read", - "x-immich-state": "Alpha" + "x-immich-permission": "plugin.read" } }, "/queues": { @@ -14722,8 +14820,56 @@ "/workflows": { "get": { "description": "Retrieve a list of workflows available to the authenticated user.", - "operationId": "getWorkflows", - "parameters": [], + "operationId": "searchWorkflows", + "parameters": [ + { + "name": "description", + "required": false, + "in": "query", + "description": "Workflow description", + "schema": { + "type": "string" + } + }, + { + "name": "enabled", + "required": false, + "in": "query", + "description": "Workflow enabled", + "schema": { + "type": "boolean" + } + }, + { + "name": "id", + "required": false, + "in": "query", + "description": "Workflow ID", + "schema": { + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", + "type": "string" + } + }, + { + "name": "name", + "required": false, + "in": "query", + "description": "Workflow name", + "schema": { + "type": "string" + } + }, + { + "name": "trigger", + "required": false, + "in": "query", + "description": "Workflow trigger type", + "schema": { + "$ref": "#/components/schemas/WorkflowTrigger" + } + } + ], "responses": { "200": { "content": { @@ -14756,16 +14902,11 @@ ], "x-immich-history": [ { - "version": "v2.3.0", + "version": "v3.0.0", "state": "Added" - }, - { - "version": "v2.3.0", - "state": "Alpha" } ], - "x-immich-permission": "workflow.read", - "x-immich-state": "Alpha" + "x-immich-permission": "workflow.read" }, "post": { "description": "Create a new workflow, the workflow can also be created with empty filters and actions.", @@ -14810,16 +14951,54 @@ ], "x-immich-history": [ { - "version": "v2.3.0", + "version": "v3.0.0", "state": "Added" - }, - { - "version": "v2.3.0", - "state": "Alpha" } ], - "x-immich-permission": "workflow.create", - "x-immich-state": "Alpha" + "x-immich-permission": "workflow.create" + } + }, + "/workflows/triggers": { + "get": { + "description": "Retrieve a list of all available workflow triggers.", + "operationId": "getWorkflowTriggers", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/WorkflowTriggerResponseDto" + }, + "type": "array" + } + } + }, + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "summary": "List all workflow triggers", + "tags": [ + "Workflows" + ], + "x-immich-history": [ + { + "version": "v3.0.0", + "state": "Added" + } + ] } }, "/workflows/{id}": { @@ -14860,16 +15039,11 @@ ], "x-immich-history": [ { - "version": "v2.3.0", + "version": "v3.0.0", "state": "Added" - }, - { - "version": "v2.3.0", - "state": "Alpha" } ], - "x-immich-permission": "workflow.delete", - "x-immich-state": "Alpha" + "x-immich-permission": "workflow.delete" }, "get": { "description": "Retrieve information about a specific workflow by its ID.", @@ -14915,16 +15089,11 @@ ], "x-immich-history": [ { - "version": "v2.3.0", + "version": "v3.0.0", "state": "Added" - }, - { - "version": "v2.3.0", - "state": "Alpha" } ], - "x-immich-permission": "workflow.read", - "x-immich-state": "Alpha" + "x-immich-permission": "workflow.read" }, "put": { "description": "Update the information of a specific workflow by its ID. This endpoint can be used to update the workflow name, description, trigger type, filters and actions order, etc.", @@ -14980,16 +15149,63 @@ ], "x-immich-history": [ { - "version": "v2.3.0", + "version": "v3.0.0", "state": "Added" - }, - { - "version": "v2.3.0", - "state": "Alpha" } ], - "x-immich-permission": "workflow.update", - "x-immich-state": "Alpha" + "x-immich-permission": "workflow.update" + } + }, + "/workflows/{id}/share": { + "get": { + "description": "Retrieve a workflow details without ids, default values, etc.", + "operationId": "getWorkflowForShare", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkflowShareResponseDto" + } + } + }, + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "summary": "Retrieve a workflow", + "tags": [ + "Workflows" + ], + "x-immich-history": [ + { + "version": "v3.0.0", + "state": "Added" + } + ], + "x-immich-permission": "workflow.read" } } }, @@ -17891,7 +18107,7 @@ "VersionCheck", "OcrQueueAll", "Ocr", - "WorkflowRun" + "WorkflowAssetCreate" ], "type": "string" }, @@ -19809,206 +20025,59 @@ ], "type": "object" }, - "PluginActionResponseDto": { + "PluginMethodResponseDto": { "properties": { "description": { - "description": "Action description", + "description": "Description", "type": "string" }, - "id": { - "description": "Action ID", - "type": "string" - }, - "methodName": { - "description": "Method name", - "type": "string" - }, - "pluginId": { - "description": "Plugin ID", - "type": "string" - }, - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/PluginJsonSchema" - } - ], - "description": "Action schema", - "nullable": true - }, - "supportedContexts": { - "description": "Supported contexts", - "items": { - "$ref": "#/components/schemas/PluginContextType" - }, - "type": "array" - }, - "title": { - "description": "Action title", - "type": "string" - } - }, - "required": [ - "description", - "id", - "methodName", - "pluginId", - "schema", - "supportedContexts", - "title" - ], - "type": "object" - }, - "PluginConfigValue": {}, - "PluginContextType": { - "description": "Plugin context", - "enum": [ - "asset", - "album", - "person" - ], - "type": "string" - }, - "PluginFilterResponseDto": { - "properties": { - "description": { - "description": "Filter description", - "type": "string" - }, - "id": { - "description": "Filter ID", - "type": "string" - }, - "methodName": { - "description": "Method name", - "type": "string" - }, - "pluginId": { - "description": "Plugin ID", - "type": "string" - }, - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/PluginJsonSchema" - } - ], - "description": "Filter schema", - "nullable": true - }, - "supportedContexts": { - "description": "Supported contexts", - "items": { - "$ref": "#/components/schemas/PluginContextType" - }, - "type": "array" - }, - "title": { - "description": "Filter title", - "type": "string" - } - }, - "required": [ - "description", - "id", - "methodName", - "pluginId", - "schema", - "supportedContexts", - "title" - ], - "type": "object" - }, - "PluginJsonSchema": { - "properties": { - "additionalProperties": { + "hostFunctions": { "type": "boolean" }, - "description": { + "key": { + "description": "Key", "type": "string" }, - "properties": { - "additionalProperties": { - "$ref": "#/components/schemas/PluginJsonSchemaProperty" - }, - "type": "object" - }, - "required": { - "items": { - "type": "string" - }, - "type": "array" - }, - "type": { - "$ref": "#/components/schemas/PluginJsonSchemaType" - } - }, - "type": "object" - }, - "PluginJsonSchemaProperty": { - "properties": { - "additionalProperties": { - "anyOf": [ - { - "type": "boolean" - }, - { - "$ref": "#/components/schemas/PluginJsonSchemaProperty" - } - ] - }, - "default": {}, - "description": { + "name": { + "description": "Name", "type": "string" }, - "enum": { - "items": { - "type": "string" - }, - "type": "array" - }, - "items": { - "$ref": "#/components/schemas/PluginJsonSchemaProperty" - }, - "properties": { - "additionalProperties": { - "$ref": "#/components/schemas/PluginJsonSchemaProperty" - }, + "schema": { + "properties": {}, "type": "object" }, - "required": { + "title": { + "description": "Title", + "type": "string" + }, + "types": { + "description": "Workflow types", + "items": { + "$ref": "#/components/schemas/WorkflowType" + }, + "type": "array" + }, + "uiHints": { + "description": "Ui hints", "items": { "type": "string" }, "type": "array" - }, - "type": { - "$ref": "#/components/schemas/PluginJsonSchemaType" } }, - "type": "object" - }, - "PluginJsonSchemaType": { - "enum": [ - "string", - "number", - "integer", - "boolean", - "object", - "array", - "null" + "required": [ + "description", + "hostFunctions", + "key", + "name", + "title", + "types", + "uiHints" ], - "type": "string" + "type": "object" }, "PluginResponseDto": { "properties": { - "actions": { - "description": "Plugin actions", - "items": { - "$ref": "#/components/schemas/PluginActionResponseDto" - }, - "type": "array" - }, "author": { "description": "Plugin author", "type": "string" @@ -20021,17 +20090,17 @@ "description": "Plugin description", "type": "string" }, - "filters": { - "description": "Plugin filters", - "items": { - "$ref": "#/components/schemas/PluginFilterResponseDto" - }, - "type": "array" - }, "id": { "description": "Plugin ID", "type": "string" }, + "methods": { + "description": "Plugin methods", + "items": { + "$ref": "#/components/schemas/PluginMethodResponseDto" + }, + "type": "array" + }, "name": { "description": "Plugin name", "type": "string" @@ -20050,12 +20119,11 @@ } }, "required": [ - "actions", "author", "createdAt", "description", - "filters", "id", + "methods", "name", "title", "updatedAt", @@ -20063,29 +20131,6 @@ ], "type": "object" }, - "PluginTriggerResponseDto": { - "properties": { - "contextType": { - "$ref": "#/components/schemas/PluginContextType" - }, - "type": { - "$ref": "#/components/schemas/PluginTriggerType" - } - }, - "required": [ - "contextType", - "type" - ], - "type": "object" - }, - "PluginTriggerType": { - "description": "Plugin trigger type", - "enum": [ - "AssetCreate", - "PersonRecognized" - ], - "type": "string" - }, "PurchaseResponse": { "properties": { "hideBuyButtonUntil": { @@ -26009,196 +26054,53 @@ ], "type": "string" }, - "WorkflowActionConfig": { - "additionalProperties": { - "$ref": "#/components/schemas/PluginConfigValue" - }, - "type": "object" - }, - "WorkflowActionItemDto": { - "properties": { - "actionConfig": { - "$ref": "#/components/schemas/WorkflowActionConfig" - }, - "pluginActionId": { - "description": "Plugin action ID", - "format": "uuid", - "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", - "type": "string" - } - }, - "required": [ - "pluginActionId" - ], - "type": "object" - }, - "WorkflowActionResponseDto": { - "properties": { - "actionConfig": { - "allOf": [ - { - "$ref": "#/components/schemas/WorkflowActionConfig" - } - ], - "nullable": true - }, - "id": { - "description": "Action ID", - "type": "string" - }, - "order": { - "description": "Action order", - "maximum": 9007199254740991, - "minimum": -9007199254740991, - "type": "integer" - }, - "pluginActionId": { - "description": "Plugin action ID", - "type": "string" - }, - "workflowId": { - "description": "Workflow ID", - "type": "string" - } - }, - "required": [ - "actionConfig", - "id", - "order", - "pluginActionId", - "workflowId" - ], - "type": "object" - }, "WorkflowCreateDto": { "properties": { - "actions": { - "description": "Workflow actions", - "items": { - "$ref": "#/components/schemas/WorkflowActionItemDto" - }, - "type": "array" - }, "description": { "description": "Workflow description", + "nullable": true, "type": "string" }, "enabled": { "description": "Workflow enabled", "type": "boolean" }, - "filters": { - "description": "Workflow filters", + "name": { + "description": "Workflow name", + "nullable": true, + "type": "string" + }, + "steps": { "items": { - "$ref": "#/components/schemas/WorkflowFilterItemDto" + "$ref": "#/components/schemas/WorkflowStepDto" }, "type": "array" }, - "name": { - "description": "Workflow name", - "type": "string" - }, - "triggerType": { - "$ref": "#/components/schemas/PluginTriggerType" + "trigger": { + "$ref": "#/components/schemas/WorkflowTrigger", + "description": "Workflow trigger type" } }, "required": [ - "actions", - "filters", - "name", - "triggerType" - ], - "type": "object" - }, - "WorkflowFilterConfig": { - "additionalProperties": { - "$ref": "#/components/schemas/PluginConfigValue" - }, - "type": "object" - }, - "WorkflowFilterItemDto": { - "properties": { - "filterConfig": { - "$ref": "#/components/schemas/WorkflowFilterConfig" - }, - "pluginFilterId": { - "description": "Plugin filter ID", - "format": "uuid", - "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", - "type": "string" - } - }, - "required": [ - "pluginFilterId" - ], - "type": "object" - }, - "WorkflowFilterResponseDto": { - "properties": { - "filterConfig": { - "allOf": [ - { - "$ref": "#/components/schemas/WorkflowFilterConfig" - } - ], - "nullable": true - }, - "id": { - "description": "Filter ID", - "type": "string" - }, - "order": { - "description": "Filter order", - "maximum": 9007199254740991, - "minimum": -9007199254740991, - "type": "integer" - }, - "pluginFilterId": { - "description": "Plugin filter ID", - "type": "string" - }, - "workflowId": { - "description": "Workflow ID", - "type": "string" - } - }, - "required": [ - "filterConfig", - "id", - "order", - "pluginFilterId", - "workflowId" + "trigger" ], "type": "object" }, "WorkflowResponseDto": { "properties": { - "actions": { - "description": "Workflow actions", - "items": { - "$ref": "#/components/schemas/WorkflowActionResponseDto" - }, - "type": "array" - }, "createdAt": { "description": "Creation date", "type": "string" }, "description": { "description": "Workflow description", + "nullable": true, "type": "string" }, "enabled": { "description": "Workflow enabled", "type": "boolean" }, - "filters": { - "description": "Workflow filters", - "items": { - "$ref": "#/components/schemas/WorkflowFilterResponseDto" - }, - "type": "array" - }, "id": { "description": "Workflow ID", "type": "string" @@ -26208,57 +26110,173 @@ "nullable": true, "type": "string" }, - "ownerId": { - "description": "Owner user ID", - "type": "string" - }, - "triggerType": { - "$ref": "#/components/schemas/PluginTriggerType" - } - }, - "required": [ - "actions", - "createdAt", - "description", - "enabled", - "filters", - "id", - "name", - "ownerId", - "triggerType" - ], - "type": "object" - }, - "WorkflowUpdateDto": { - "properties": { - "actions": { - "description": "Workflow actions", + "steps": { + "description": "Workflow steps", "items": { - "$ref": "#/components/schemas/WorkflowActionItemDto" + "$ref": "#/components/schemas/WorkflowStepDto" }, "type": "array" }, + "trigger": { + "$ref": "#/components/schemas/WorkflowTrigger", + "description": "Workflow trigger type" + }, + "updatedAt": { + "description": "Update date", + "type": "string" + } + }, + "required": [ + "createdAt", + "description", + "enabled", + "id", + "name", + "steps", + "trigger", + "updatedAt" + ], + "type": "object" + }, + "WorkflowShareResponseDto": { + "properties": { "description": { "description": "Workflow description", + "nullable": true, + "type": "string" + }, + "name": { + "description": "Workflow name", + "nullable": true, + "type": "string" + }, + "steps": { + "description": "Workflow steps", + "items": { + "$ref": "#/components/schemas/WorkflowShareStepDto" + }, + "type": "array" + }, + "trigger": { + "$ref": "#/components/schemas/WorkflowTrigger", + "description": "Workflow trigger type" + } + }, + "required": [ + "description", + "name", + "steps", + "trigger" + ], + "type": "object" + }, + "WorkflowShareStepDto": { + "properties": { + "config": { + "additionalProperties": {}, + "description": "Step configuration", + "nullable": true, + "type": "object" + }, + "enabled": { + "description": "Step is enabled", + "type": "boolean" + }, + "method": { + "description": "Step plugin method", + "type": "string" + } + }, + "required": [ + "config", + "method" + ], + "type": "object" + }, + "WorkflowStepDto": { + "properties": { + "config": { + "additionalProperties": {}, + "description": "Step configuration", + "nullable": true, + "type": "object" + }, + "enabled": { + "description": "Step is enabled", + "type": "boolean" + }, + "method": { + "description": "Step plugin method", + "type": "string" + } + }, + "required": [ + "config", + "method" + ], + "type": "object" + }, + "WorkflowTrigger": { + "description": "Plugin trigger type", + "enum": [ + "AssetCreate", + "PersonRecognized" + ], + "type": "string" + }, + "WorkflowTriggerResponseDto": { + "properties": { + "trigger": { + "$ref": "#/components/schemas/WorkflowTrigger", + "description": "Trigger type" + }, + "types": { + "description": "Workflow types", + "items": { + "$ref": "#/components/schemas/WorkflowType" + }, + "type": "array" + } + }, + "required": [ + "trigger", + "types" + ], + "type": "object" + }, + "WorkflowType": { + "description": "Workflow type", + "enum": [ + "AssetV1", + "AssetPersonV1" + ], + "type": "string" + }, + "WorkflowUpdateDto": { + "properties": { + "description": { + "description": "Workflow description", + "nullable": true, "type": "string" }, "enabled": { "description": "Workflow enabled", "type": "boolean" }, - "filters": { - "description": "Workflow filters", + "name": { + "description": "Workflow name", + "nullable": true, + "type": "string" + }, + "steps": { "items": { - "$ref": "#/components/schemas/WorkflowFilterItemDto" + "$ref": "#/components/schemas/WorkflowStepDto" }, "type": "array" }, - "name": { - "description": "Workflow name", - "type": "string" - }, - "triggerType": { - "$ref": "#/components/schemas/PluginTriggerType" + "trigger": { + "$ref": "#/components/schemas/WorkflowTrigger", + "description": "Workflow trigger type" } }, "type": "object" diff --git a/package.json b/package.json index 4d361b329d..c6d1041f5a 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "format": "prettier --cache --check i18n/", "format:fix": "prettier --cache --write --list-different i18n" }, - "packageManager": "pnpm@10.33.1+sha512.05ba3c1d5d1c18f68df06470d74055e62d41fc110a0c660db1b2dfb2785327f04cf0f68345d4609bc52089e7fa0343c31593b2f9594e2c5d5da426230acc9820", + "packageManager": "pnpm@10.33.4+sha512.1c67b3b359b2d408119ba1ed289f34b8fc3c6873412bec6fd264fbdc82489e510fcbecb9ce9d22dae7f3b76269d8441046014bdca53b9979cd7a561ad631b800", "engines": { "pnpm": ">=10.0.0" }, diff --git a/packages/cli/package.json b/packages/cli/package.json index 534b213f2b..988cf034a5 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -25,7 +25,7 @@ "@types/lodash-es": "^4.17.12", "@types/micromatch": "^4.0.9", "@types/mock-fs": "^4.13.1", - "@types/node": "^24.12.2", + "@types/node": "^24.12.4", "@vitest/coverage-v8": "^4.0.0", "byte-size": "^9.0.0", "cli-progress": "^3.12.0", diff --git a/packages/e2e-auth-server/package.json b/packages/e2e-auth-server/package.json index 0e1928d34c..c9df258557 100644 --- a/packages/e2e-auth-server/package.json +++ b/packages/e2e-auth-server/package.json @@ -13,5 +13,5 @@ "oidc-provider": "^9.0.0", "tsx": "^4.20.6" }, - "packageManager": "pnpm@10.33.1" + "packageManager": "pnpm@10.33.4" } diff --git a/packages/plugin-core/.gitignore b/packages/plugin-core/.gitignore new file mode 100644 index 0000000000..c925c21d56 --- /dev/null +++ b/packages/plugin-core/.gitignore @@ -0,0 +1,2 @@ +/dist +/node_modules diff --git a/packages/plugin-core/.prettierrc b/packages/plugin-core/.prettierrc new file mode 100644 index 0000000000..7cebf3813c --- /dev/null +++ b/packages/plugin-core/.prettierrc @@ -0,0 +1,6 @@ +{ + "singleQuote": true, + "trailingComma": "all", + "printWidth": 120, + "semi": true +} diff --git a/packages/plugin-core/esbuild.js b/packages/plugin-core/esbuild.js new file mode 100644 index 0000000000..4e1eda47e2 --- /dev/null +++ b/packages/plugin-core/esbuild.js @@ -0,0 +1,11 @@ +const esbuild = require('esbuild'); + +esbuild.build({ + entryPoints: ['src/index.ts'], + outdir: 'dist', + bundle: true, + sourcemap: false, + minify: false, // might want to use true for production build + format: 'cjs', // needs to be CJS for now + target: ['es2020'], // don't go over es2020 because quickjs doesn't support it +}); diff --git a/packages/plugin-core/manifest.json b/packages/plugin-core/manifest.json new file mode 100644 index 0000000000..1dc6d21409 --- /dev/null +++ b/packages/plugin-core/manifest.json @@ -0,0 +1,258 @@ +{ + "name": "immich-plugin-core", + "version": "2.0.1", + "title": "Immich Core Plugin", + "description": "Core workflow capabilities for Immich", + "author": "Immich Team", + "wasmPath": "dist/plugin.wasm", + "methods": [ + { + "name": "assetFileFilter", + "title": "Filter by filename", + "description": "Filter assets by filename pattern using text matching or regular expressions", + "types": ["AssetV1"], + "schema": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "title": "Filename pattern", + "description": "Text or regex pattern to match against filename" + }, + "matchType": { + "type": "string", + "title": "Match type", + "enum": ["contains", "startsWith", "exact", "regex"], + "default": "contains", + "description": "Type of pattern matching to perform" + }, + "caseSensitive": { + "type": "boolean", + "default": false, + "title": "Case sensitive", + "description": "Whether matching should be case-sensitive" + } + }, + "required": ["pattern"] + }, + "uiHints": ["filter"] + }, + { + "name": "filterFileType", + "title": "Filter by file type", + "description": "Filter assets by file type", + "types": ["AssetV1"], + "schema": { + "type": "object", + "properties": { + "fileTypes": { + "title": "File types", + "description": "Allowed file types", + "type": "string", + "array": true, + "enum": ["image", "video"] + } + }, + "required": ["fileTypes"] + }, + "uiHints": ["filter"] + }, + { + "name": "filterPerson", + "title": "Filter by person", + "description": "Filter by detected person", + "types": ["AssetV1"], + "schema": { + "properties": { + "personIds": { + "type": "string", + "array": true, + "title": "Person IDs", + "description": "List of person to match", + "uiHint": "personI" + }, + "matchAny": { + "type": "boolean", + "title": "Match any", + "default": true, + "description": "Match any name (true) or require all names (false)" + } + }, + "required": ["personIds"] + }, + "uiHints": ["filter"] + }, + { + "name": "assetArchive", + "title": "Archive asset", + "description": "Change asset visibility to archive", + "types": ["AssetV1"], + "schema": { + "properties": { + "inverse": { + "title": "Inverse", + "description": "When true will unarchive any archived assets", + "type": "boolean" + } + } + } + }, + { + "name": "assetLock", + "title": "Move to locked folder", + "description": "Change visibility to locked", + "types": ["AssetV1"] + }, + { + "name": "assetTimeline", + "title": "Move to timeline", + "description": "Change visibility to timeline", + "types": ["AssetV1"] + }, + { + "name": "assetVisibility", + "title": "Update visibility", + "description": "Change visibility to selected option", + "types": ["AssetV1"], + "schema": { + "properties": { + "visibility": { + "title": "Visibility", + "description": "Asset visibility", + "type": "string", + "enum": ["archive", "timeline", "locked"] + } + }, + "required": ["visibility"] + } + }, + { + "name": "assetFavorite", + "title": "Favorite", + "description": "Favorite an asset", + "types": ["AssetV1"], + "schema": { + "type": "object", + "properties": { + "inverse": { + "type": "boolean", + "title": "Inverse", + "description": "Unfavorite by default, set to true to favorite instead", + "default": false + } + } + } + }, + { + "name": "assetAddToAlbums", + "title": "Add to Album(s)", + "description": "Add asset to selected albums", + "types": ["AssetV1"], + "hostFunctions": true, + "schema": { + "type": "object", + "properties": { + "albumIds": { + "type": "string", + "title": "Album IDs", + "array": true, + "description": "Target album IDs", + "uiHint": "albumId" + } + }, + "required": ["albumIds"] + } + }, + { + "name": "noop1", + "title": "DEV: Nested properties", + "description": "Example configuration with nested properties", + "types": ["AssetV1"], + "schema": { + "type": "object", + "properties": { + "number1": { + "type": "number", + "title": "Number 1", + "description": "Basic number" + }, + "number2": { + "type": "number", + "title": "Number 2", + "array": true, + "description": "List of numbers" + }, + "string1": { + "type": "string", + "title": "String 1", + "description": "Basic string" + }, + "string2": { + "type": "string", + "title": "String 2", + "array": true, + "description": "List of strings" + }, + "string3": { + "type": "string", + "title": "String 3", + "enum": ["choice-1", "choice-2"], + "description": "Select from a list" + }, + "nested": { + "type": "object", + "title": "Nested", + "description": "Nested properties for nesting", + "properties": { + "nested1": { + "type": "string", + "title": "Nested 1", + "description": "Nested string" + }, + "nested2": { + "type": "number", + "title": "Nested 2", + "description": "Nested number" + }, + "nested3": { + "type": "object", + "title": "Nested 3", + "description": "Nested again", + "properties": { + "nested4": { + "type": "boolean", + "title": "Nested 4", + "description": "Nested, nested boolean" + } + } + } + } + } + } + } + }, + { + "name": "noop2", + "title": "DEV: Album pickers", + "description": "Example configuration with album pickers", + "types": ["AssetV1"], + "schema": { + "properties": { + "albumId": { + "type": "string", + "title": "Album ID", + "description": "Target album ID", + "uiHint": "albumId" + }, + "albumIds": { + "type": "string", + "title": "Album IDs", + "description": "Target album IDs", + "array": true, + "uiHint": "albumId" + } + } + } + } + ] +} diff --git a/packages/plugin-core/mise.toml b/packages/plugin-core/mise.toml new file mode 100644 index 0000000000..cb69c36ee7 --- /dev/null +++ b/packages/plugin-core/mise.toml @@ -0,0 +1,6 @@ +[tasks.install] +run = "pnpm install --frozen-lockfile" + +[tasks.build] +depends = ["install"] +run = "pnpm --filter @immich/plugin-sdk --filter @immich/plugin-core build" diff --git a/packages/plugins/package.json b/packages/plugin-core/package.json similarity index 85% rename from packages/plugins/package.json rename to packages/plugin-core/package.json index 2aa1bd599f..7c0bdf9af2 100644 --- a/packages/plugins/package.json +++ b/packages/plugin-core/package.json @@ -1,5 +1,5 @@ { - "name": "plugins", + "name": "@immich/plugin-core", "version": "1.0.0", "description": "", "main": "src/index.ts", @@ -13,6 +13,7 @@ "license": "AGPL-3.0", "devDependencies": { "@extism/js-pdk": "^1.0.1", + "@immich/plugin-sdk": "workspace:*", "esbuild": "^0.28.0", "typescript": "^6.0.0" } diff --git a/packages/plugin-core/src/index.d.ts b/packages/plugin-core/src/index.d.ts new file mode 100644 index 0000000000..ae45184cbe --- /dev/null +++ b/packages/plugin-core/src/index.d.ts @@ -0,0 +1,19 @@ +// copy from +// import '@immich/plugin-sdk/host-functions'; +declare module 'extism:host' { + interface user { + albumAddAssets(ptr: PTR): I64; + addAssetsToAlbums(ptr: PTR): I64; + } +} + +declare module 'main' { + export function assetFileFilter(): I32; + export function assetFavorite(): I32; + export function assetVisibility(): I32; + export function assetArchive(): I32; + export function assetLock(): I32; + export function assetTimeline(): I32; + export function assetTrash(): I32; + export function assetAddToAlbums(): I32; +} diff --git a/packages/plugin-core/src/index.ts b/packages/plugin-core/src/index.ts new file mode 100644 index 0000000000..de54e52bb0 --- /dev/null +++ b/packages/plugin-core/src/index.ts @@ -0,0 +1,111 @@ +import { AssetStatus, AssetVisibility, WorkflowType, wrapper } from '@immich/plugin-sdk'; + +type AssetFileFilterConfig = { + pattern: string; + matchType?: 'contains' | 'exact' | 'regex' | 'startsWith'; + caseSensitive?: boolean; +}; +export const assetFileFilter = () => { + return wrapper(({ data, config }) => { + const { pattern, matchType = 'contains', caseSensitive = false } = config; + + const { asset } = data; + + const fileName = asset.originalFileName || ''; + const searchName = caseSensitive ? fileName : fileName.toLowerCase(); + const searchPattern = caseSensitive ? pattern : pattern.toLowerCase(); + + switch (matchType) { + case 'contains': { + return { workflow: { continue: searchName.includes(searchPattern) } }; + } + + case 'exact': { + return { workflow: { continue: searchName === searchPattern } }; + } + + case 'startsWith': { + return { workflow: { continue: searchName.startsWith(searchPattern) } }; + } + + case 'regex': { + const flags = caseSensitive ? '' : 'i'; + const regex = new RegExp(searchPattern, flags); + return { workflow: { continue: regex.test(fileName) } }; + } + + default: { + return {}; + } + } + }); +}; + +export const assetFavorite = () => { + return wrapper(({ config, data }) => { + const target = config.inverse ? false : true; + if (target !== data.asset.isFavorite) { + return { + changes: { + asset: { isFavorite: target }, + }, + }; + } + }); +}; + +export const assetVisibility = () => { + return wrapper(({ config }) => ({ + changes: { asset: { visibility: config.visibility } }, + })); +}; + +export const assetArchive = () => { + return wrapper(({ config, data }) => { + if (!config.inverse && data.asset.visibility !== AssetVisibility.Archive) { + return { changes: { asset: { visibility: AssetVisibility.Archive } } }; + } + + if (config.inverse && data.asset.visibility === AssetVisibility.Archive) { + return { changes: { asset: { visibility: AssetVisibility.Timeline } } }; + } + + return {}; + }); +}; + +export const assetLock = () => { + return wrapper(({ config, data }) => { + if (!config.inverse && data.asset.visibility !== AssetVisibility.Locked) { + return { changes: { asset: { visibility: AssetVisibility.Locked } } }; + } + + if (config.inverse && data.asset.visibility === AssetVisibility.Locked) { + return { changes: { asset: { visibility: AssetVisibility.Timeline } } }; + } + + return {}; + }); +}; + +export const assetTrash = () => { + return wrapper(({ config, data }) => ({ + changes: { + asset: config.inverse + ? { deletedAt: null, status: AssetStatus.Active } + : { deletedAt: new Date(), status: AssetStatus.Trashed }, + }, + })); +}; + +export const assetAddToAlbums = () => { + return wrapper(({ config, data, functions }) => { + if (config.albumIds.length === 1) { + functions.albumAddAssets(config.albumIds[0], [data.asset.id]); + return {}; + } + + functions.addAssetsToAlbums({ albumIds: config.albumIds, assetIds: [data.asset.id] }); + return {}; + }); +}; diff --git a/packages/plugins/tsconfig.json b/packages/plugin-core/tsconfig.json similarity index 68% rename from packages/plugins/tsconfig.json rename to packages/plugin-core/tsconfig.json index a0bc730661..23ab692016 100644 --- a/packages/plugins/tsconfig.json +++ b/packages/plugin-core/tsconfig.json @@ -1,20 +1,24 @@ { "compilerOptions": { - "target": "es2020", // Specify ECMAScript target version - "module": "commonjs", // Specify module code generation - "lib": ["es2020"], // Specify a list of library files to be included in the compilation - "types": ["./src/index.d.ts", "./node_modules/@extism/js-pdk"], // Specify a list of type definition files to be included in the compilation - "strict": true, // Enable all strict type-checking options - "esModuleInterop": true, // Enables compatibility with Babel-style module imports - "skipLibCheck": true, // Skip type checking of declaration files "allowJs": true, // Allow JavaScript files to be compiled + "declaration": true, + "emitDeclarationOnly": true, + "esModuleInterop": true, // Enables compatibility with Babel-style module imports + "lib": ["es2020"], // Specify a list of library files to be included in the compilation + "module": "nodenext", // Specify module code generation + "moduleResolution": "nodenext", + "noEmit": true, // Do not emit outputs (no .js or .d.ts files) + "outDir": "./dist", "rootDir": "./src", - "noEmit": true // Do not emit outputs (no .js or .d.ts files) + "skipLibCheck": true, // Skip type checking of declaration files + "strict": true, // Enable all strict type-checking options + "target": "es2020", // Specify ECMAScript target version + "types": ["./src/index.d.ts", "./node_modules/@extism/js-pdk"] // Specify a list of type definition files to be included in the compilation }, - "include": [ - "src/**/*.ts" // Include all TypeScript files in src directory - ], "exclude": [ "node_modules" // Exclude the node_modules directory + ], + "include": [ + "src/**/*.ts" // Include all TypeScript files in src directory ] } diff --git a/packages/plugin-sdk/.gitignore b/packages/plugin-sdk/.gitignore new file mode 100644 index 0000000000..c925c21d56 --- /dev/null +++ b/packages/plugin-sdk/.gitignore @@ -0,0 +1,2 @@ +/dist +/node_modules diff --git a/packages/plugin-sdk/esbuild.js b/packages/plugin-sdk/esbuild.js new file mode 100644 index 0000000000..d2e036e5c7 --- /dev/null +++ b/packages/plugin-sdk/esbuild.js @@ -0,0 +1,11 @@ +import esbuild from 'esbuild'; + +esbuild.build({ + entryPoints: ['src/index.ts'], + outdir: 'dist', + bundle: true, + sourcemap: false, + minify: false, + format: 'esm', + target: ['es2020'], +}); diff --git a/packages/plugin-sdk/package.json b/packages/plugin-sdk/package.json new file mode 100644 index 0000000000..f505a6cc0e --- /dev/null +++ b/packages/plugin-sdk/package.json @@ -0,0 +1,38 @@ +{ + "name": "@immich/plugin-sdk", + "version": "0.0.0", + "description": "", + "main": "index.js", + "type": "module", + "exports": { + "./host-functions": { + "import": "./dist/host-functions.js", + "types": "./dist/host-functions.d.ts" + }, + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "node esbuild.js && tsc --emitDeclarationOnly && tsc-alias" + }, + "files": [ + "dist" + ], + "keywords": [], + "author": "", + "license": "GNU Affero General Public License version 3", + "packageManager": "pnpm@10.33.4", + "devDependencies": { + "@extism/js-pdk": "^1.1.1", + "@types/node": "^24.12.4", + "esbuild": "^0.28.0", + "tsc-alias": "^1.8.16", + "typescript": "^5.9.3" + }, + "peerDependencies": { + "@extism/js-pdk": "^1.1.1" + } +} diff --git a/packages/plugin-sdk/src/enum.ts b/packages/plugin-sdk/src/enum.ts new file mode 100644 index 0000000000..a11dab64da --- /dev/null +++ b/packages/plugin-sdk/src/enum.ts @@ -0,0 +1,33 @@ +export enum WorkflowTrigger { + AssetCreate = 'AssetCreate', + PersonRecognized = 'PersonRecognized', +} + +export enum WorkflowType { + AssetV1 = 'AssetV1', + AssetPersonV1 = 'AssetPersonV1', +} + +export enum AssetType { + Image = 'IMAGE', + Video = 'VIDEO', + Audio = 'AUDIO', + Other = 'OTHER', +} + +export enum AssetStatus { + Active = 'active', + Trashed = 'trashed', + Deleted = 'deleted', +} + +export enum AssetVisibility { + Archive = 'archive', + Timeline = 'timeline', + + /** + * Video part of the LivePhotos and MotionPhotos + */ + Hidden = 'hidden', + Locked = 'locked', +} diff --git a/packages/plugin-sdk/src/host-functions.ts b/packages/plugin-sdk/src/host-functions.ts new file mode 100644 index 0000000000..d0f8a3ef17 --- /dev/null +++ b/packages/plugin-sdk/src/host-functions.ts @@ -0,0 +1,51 @@ +declare module 'extism:host' { + interface user { + albumAddAssets(ptr: PTR): I64; + addAssetsToAlbums(ptr: PTR): I64; + } +} + +const host = Host.getFunctions(); +type HostFunctionName = keyof typeof host; +type HostFunctionSuccessResult = { success: true; response: T }; +type HostFunctionErrorResult = { + success: false; + status: number; + message: string; +}; +type HostFunctionResult = + | HostFunctionSuccessResult + | HostFunctionErrorResult; + +const call = (name: HostFunctionName, authToken: string, args: T) => { + const pointer1 = Memory.fromString(JSON.stringify({ authToken, args })); + const fn = host[name]; + const handler = Memory.find(fn(pointer1.offset)); + + try { + const result = JSON.parse(handler.readString()) as HostFunctionResult; + + if (result.success) { + return result.response; + } + + throw new Error( + `Failed to call host function "${String(name)}", received ${result.status} - ${JSON.stringify(result.message)}`, + ); + } finally { + handler.free(); + pointer1.free(); + } +}; + +type AlbumsToAssets = { + assetIds: string[]; + albumIds: string[]; +}; + +export const hostFunctions = (authToken: string) => ({ + albumAddAssets: (albumId: string, assetIds: string[]) => + call('albumAddAssets', authToken, [albumId, { ids: assetIds }]), + addAssetsToAlbums: ({ assetIds, albumIds }: AlbumsToAssets) => + call('addAssetsToAlbums', authToken, [{ albumIds, assetIds }]), +}); diff --git a/packages/plugin-sdk/src/index.ts b/packages/plugin-sdk/src/index.ts new file mode 100644 index 0000000000..6d4deb2053 --- /dev/null +++ b/packages/plugin-sdk/src/index.ts @@ -0,0 +1,4 @@ +export * from 'src/enum.js'; +export * from 'src/host-functions.js'; +export * from 'src/sdk.js'; +export * from 'src/types.js'; diff --git a/packages/plugin-sdk/src/sdk.ts b/packages/plugin-sdk/src/sdk.ts new file mode 100644 index 0000000000..f0ff8723a6 --- /dev/null +++ b/packages/plugin-sdk/src/sdk.ts @@ -0,0 +1,43 @@ +import type { WorkflowType } from 'src/enum.js'; +import { hostFunctions } from 'src/host-functions.js'; +import type { + ConfigValue, + WorkflowEventPayload, + WorkflowResponse, +} from 'src/types.js'; + +export const wrapper = < + T extends WorkflowType = WorkflowType, + TConfig extends ConfigValue = ConfigValue, +>( + fn: ( + payload: WorkflowEventPayload & { + functions: ReturnType; + }, + ) => WorkflowResponse | undefined, +) => { + const input = Host.inputString(); + + try { + const event = JSON.parse(input) as WorkflowEventPayload; + // const debug = event.workflow.debug ?? false; + + console.debug( + `Inputs: trigger=${event.trigger}, event=${event.type}, config=${JSON.stringify(event.config)}`, + ); + + const response = + fn({ ...event, functions: hostFunctions(event.workflow.authToken) }) ?? + {}; + + console.debug( + `Outputs: workflow=${JSON.stringify(response.workflow)}, changes=${JSON.stringify(response.changes)}, data=${JSON.stringify(response.data)}`, + ); + + const output = JSON.stringify(response); + Host.outputString(output); + } catch (error: Error | any) { + console.error(`Unhandled plugin exception: ${error.message || error}`); + throw error; + } +}; diff --git a/packages/plugin-sdk/src/types.ts b/packages/plugin-sdk/src/types.ts new file mode 100644 index 0000000000..54cca3a5aa --- /dev/null +++ b/packages/plugin-sdk/src/types.ts @@ -0,0 +1,129 @@ +import type { + AssetStatus, + AssetType, + AssetVisibility, + WorkflowTrigger, + WorkflowType, +} from 'src/enum.js'; + +type DeepPartial = T extends Date + ? T + : T extends Record + ? { [K in keyof T]?: DeepPartial } + : T extends Array + ? DeepPartial[] + : T; + +export type WorkflowEventMap = { + [WorkflowType.AssetV1]: AssetV1; + [WorkflowType.AssetPersonV1]: AssetPersonV1; +}; + +export type WorkflowEventData = WorkflowEventMap[T]; + +export type WorkflowEventPayload< + T extends WorkflowType = WorkflowType, + TConfig = WorkflowStepConfig, +> = { + trigger: WorkflowTrigger; + type: T; + data: WorkflowEventData; + config: TConfig; + workflow: { + id: string; + authToken: string; + stepId: string; + debug?: boolean; + }; +}; + +export type WorkflowChanges = + DeepPartial>; + +export type WorkflowResponse = { + workflow?: { + /** stop the workflow */ + continue?: boolean; + }; + changes?: WorkflowChanges; + /** data to be passed to the next workflow step */ + data?: Record; +}; + +export type WorkflowStepConfig = { + [key: string]: ConfigValue; +}; + +export type ConfigValue = + | string + | number + | boolean + | null + | ConfigValue[] + | { [key: string]: ConfigValue }; + +export type AssetV1 = { + asset: { + id: string; + ownerId: string; + type: AssetType; + originalPath: string; + fileCreatedAt: Date; + fileModifiedAt: Date; + isFavorite: boolean; + checksum: Buffer; // sha1 checksum + livePhotoVideoId: string | null; + updatedAt: Date; + createdAt: Date; + originalFileName: string; + isOffline: boolean; + libraryId: string | null; + isExternal: boolean; + deletedAt: Date | null; + localDateTime: Date; + stackId: string | null; + duplicateId: string | null; + status: AssetStatus; + visibility: AssetVisibility; + isEdited: boolean; + exifInfo: { + make: string | null; + model: string | null; + exifImageWidth: number | null; + exifImageHeight: number | null; + fileSizeInByte: number | null; + orientation: string | null; + dateTimeOriginal: Date | null; + modifyDate: Date | null; + lensModel: string | null; + fNumber: number | null; + focalLength: number | null; + iso: number | null; + latitude: number | null; + longitude: number | null; + city: string | null; + state: string | null; + country: string | null; + description: string | null; + fps: number | null; + exposureTime: string | null; + livePhotoCID: string | null; + timeZone: string | null; + projectionType: string | null; + profileDescription: string | null; + colorspace: string | null; + bitsPerSample: number | null; + autoStackId: string | null; + rating: number | null; + tags: string[] | null; + updatedAt: Date | null; + } | null; + }; +}; + +export type AssetPersonV1 = AssetV1 & { + person: { + id: string; + name: string; + }; +}; diff --git a/packages/plugin-sdk/tsconfig.json b/packages/plugin-sdk/tsconfig.json new file mode 100644 index 0000000000..2dbd91de0a --- /dev/null +++ b/packages/plugin-sdk/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "declaration": true, + "emitDeclarationOnly": true, + "esModuleInterop": true, + "exactOptionalPropertyTypes": true, + "isolatedModules": true, + "lib": ["esnext"], + "module": "nodenext", + "moduleDetection": "force", + "noUncheckedIndexedAccess": true, + "noUncheckedSideEffectImports": true, + "outDir": "./dist", + "paths": { + "src/*": ["./src/*"] + }, + "removeComments": true, + "rootDir": "./src", + "skipLibCheck": true, + "sourceMap": false, + "strict": true, + "target": "esnext", + "types": ["node", "@extism/js-pdk"], + "verbatimModuleSyntax": true + } +} diff --git a/packages/plugins/.gitignore b/packages/plugins/.gitignore deleted file mode 100644 index 76add878f8..0000000000 --- a/packages/plugins/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -dist \ No newline at end of file diff --git a/packages/plugins/LICENSE b/packages/plugins/LICENSE deleted file mode 100644 index 53f0fa6953..0000000000 --- a/packages/plugins/LICENSE +++ /dev/null @@ -1,26 +0,0 @@ -Copyright 2024, The Extism Authors. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/plugins/esbuild.js b/packages/plugins/esbuild.js deleted file mode 100644 index 04cb6e85aa..0000000000 --- a/packages/plugins/esbuild.js +++ /dev/null @@ -1,12 +0,0 @@ -const esbuild = require('esbuild'); - -esbuild - .build({ - entryPoints: ['src/index.ts'], - outdir: 'dist', - bundle: true, - sourcemap: true, - minify: false, // might want to use true for production build - format: 'cjs', // needs to be CJS for now - target: ['es2020'] // don't go over es2020 because quickjs doesn't support it - }) \ No newline at end of file diff --git a/packages/plugins/manifest.json b/packages/plugins/manifest.json deleted file mode 100644 index 4d2de275ca..0000000000 --- a/packages/plugins/manifest.json +++ /dev/null @@ -1,159 +0,0 @@ -{ - "name": "immich-core", - "version": "2.0.1", - "title": "Immich Core", - "description": "Core workflow capabilities for Immich", - "author": "Immich Team", - "wasm": { - "path": "dist/plugin.wasm" - }, - "filters": [ - { - "methodName": "filterFileName", - "title": "Filter by filename", - "description": "Filter assets by filename pattern using text matching or regular expressions", - "supportedContexts": [ - "asset" - ], - "schema": { - "type": "object", - "properties": { - "pattern": { - "type": "string", - "title": "Filename pattern", - "description": "Text or regex pattern to match against filename" - }, - "matchType": { - "type": "string", - "title": "Match type", - "enum": [ - "contains", - "regex", - "exact" - ], - "default": "contains", - "description": "Type of pattern matching to perform" - }, - "caseSensitive": { - "type": "boolean", - "default": false, - "description": "Whether matching should be case-sensitive" - } - }, - "required": [ - "pattern" - ] - } - }, - { - "methodName": "filterFileType", - "title": "Filter by file type", - "description": "Filter assets by file type", - "supportedContexts": [ - "asset" - ], - "schema": { - "type": "object", - "properties": { - "fileTypes": { - "type": "array", - "title": "File types", - "items": { - "type": "string", - "enum": [ - "image", - "video" - ] - }, - "description": "Allowed file types" - } - }, - "required": [ - "fileTypes" - ] - } - }, - { - "methodName": "filterPerson", - "title": "Filter by person", - "description": "Filter by detected person", - "supportedContexts": [ - "person" - ], - "schema": { - "type": "object", - "properties": { - "personIds": { - "type": "array", - "title": "Person IDs", - "items": { - "type": "string" - }, - "description": "List of person to match", - "subType": "people-picker" - }, - "matchAny": { - "type": "boolean", - "default": true, - "description": "Match any name (true) or require all names (false)" - } - }, - "required": [ - "personIds" - ] - } - } - ], - "actions": [ - { - "methodName": "actionArchive", - "title": "Archive", - "description": "Move the asset to archive", - "supportedContexts": [ - "asset" - ], - "schema": {} - }, - { - "methodName": "actionFavorite", - "title": "Favorite", - "description": "Mark the asset as favorite or unfavorite", - "supportedContexts": [ - "asset" - ], - "schema": { - "type": "object", - "properties": { - "favorite": { - "type": "boolean", - "default": true, - "description": "Set favorite (true) or unfavorite (false)" - } - } - } - }, - { - "methodName": "actionAddToAlbum", - "title": "Add to Album", - "description": "Add the item to a specified album", - "supportedContexts": [ - "asset", - "person" - ], - "schema": { - "type": "object", - "properties": { - "albumId": { - "type": "string", - "title": "Album ID", - "description": "Target album ID", - "subType": "album-picker" - } - }, - "required": [ - "albumId" - ] - } - } - ] -} diff --git a/packages/plugins/mise.toml b/packages/plugins/mise.toml deleted file mode 100644 index 66a107674d..0000000000 --- a/packages/plugins/mise.toml +++ /dev/null @@ -1,11 +0,0 @@ -[tools] -"github:extism/cli" = "1.6.3" -"github:webassembly/binaryen" = "version_124" -"github:extism/js-pdk" = "1.6.0" - -[tasks.install] -run = "pnpm install --frozen-lockfile" - -[tasks.build] -depends = ["install"] -run = "pnpm run build" diff --git a/packages/plugins/package-lock.json b/packages/plugins/package-lock.json deleted file mode 100644 index d74b830799..0000000000 --- a/packages/plugins/package-lock.json +++ /dev/null @@ -1,533 +0,0 @@ -{ - "name": "plugins", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "plugins", - "version": "1.0.0", - "license": "AGPL-3.0", - "devDependencies": { - "@extism/js-pdk": "^1.0.1", - "esbuild": "^0.28.0", - "typescript": "^6.0.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", - "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", - "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", - "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", - "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", - "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", - "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", - "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", - "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", - "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", - "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", - "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", - "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", - "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", - "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", - "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", - "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", - "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", - "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", - "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", - "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", - "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", - "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", - "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", - "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", - "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", - "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@extism/js-pdk": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@extism/js-pdk/-/js-pdk-1.1.1.tgz", - "integrity": "sha512-VZLn/dX0ttA1uKk2PZeR/FL3N+nA1S5Vc7E5gdjkR60LuUIwCZT9cYON245V4HowHlBA7YOegh0TLjkx+wNbrA==", - "dev": true, - "license": "BSD-Clause-3", - "dependencies": { - "urlpattern-polyfill": "^8.0.2" - } - }, - "node_modules/esbuild": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", - "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.0", - "@esbuild/android-arm": "0.28.0", - "@esbuild/android-arm64": "0.28.0", - "@esbuild/android-x64": "0.28.0", - "@esbuild/darwin-arm64": "0.28.0", - "@esbuild/darwin-x64": "0.28.0", - "@esbuild/freebsd-arm64": "0.28.0", - "@esbuild/freebsd-x64": "0.28.0", - "@esbuild/linux-arm": "0.28.0", - "@esbuild/linux-arm64": "0.28.0", - "@esbuild/linux-ia32": "0.28.0", - "@esbuild/linux-loong64": "0.28.0", - "@esbuild/linux-mips64el": "0.28.0", - "@esbuild/linux-ppc64": "0.28.0", - "@esbuild/linux-riscv64": "0.28.0", - "@esbuild/linux-s390x": "0.28.0", - "@esbuild/linux-x64": "0.28.0", - "@esbuild/netbsd-arm64": "0.28.0", - "@esbuild/netbsd-x64": "0.28.0", - "@esbuild/openbsd-arm64": "0.28.0", - "@esbuild/openbsd-x64": "0.28.0", - "@esbuild/openharmony-arm64": "0.28.0", - "@esbuild/sunos-x64": "0.28.0", - "@esbuild/win32-arm64": "0.28.0", - "@esbuild/win32-ia32": "0.28.0", - "@esbuild/win32-x64": "0.28.0" - } - }, - "node_modules/typescript": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/urlpattern-polyfill": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-8.0.2.tgz", - "integrity": "sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==", - "dev": true, - "license": "MIT" - } - } -} diff --git a/packages/plugins/src/index.d.ts b/packages/plugins/src/index.d.ts deleted file mode 100644 index 7f805aafe6..0000000000 --- a/packages/plugins/src/index.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -declare module 'main' { - export function filterFileName(): I32; - export function actionAddToAlbum(): I32; - export function actionArchive(): I32; -} - -declare module 'extism:host' { - interface user { - updateAsset(ptr: PTR): I32; - addAssetToAlbum(ptr: PTR): I32; - } -} diff --git a/packages/plugins/src/index.ts b/packages/plugins/src/index.ts deleted file mode 100644 index 5cf666fc87..0000000000 --- a/packages/plugins/src/index.ts +++ /dev/null @@ -1,71 +0,0 @@ -const { updateAsset, addAssetToAlbum } = Host.getFunctions(); - -function parseInput() { - return JSON.parse(Host.inputString()); -} - -function returnOutput(output: any) { - Host.outputString(JSON.stringify(output)); - return 0; -} - -export function filterFileName() { - const input = parseInput(); - const { data, config } = input; - const { pattern, matchType = 'contains', caseSensitive = false } = config; - - const fileName = data.asset.originalFileName || data.asset.fileName || ''; - const searchName = caseSensitive ? fileName : fileName.toLowerCase(); - const searchPattern = caseSensitive ? pattern : pattern.toLowerCase(); - - let passed = false; - - if (matchType === 'exact') { - passed = searchName === searchPattern; - } else if (matchType === 'regex') { - const flags = caseSensitive ? '' : 'i'; - const regex = new RegExp(searchPattern, flags); - passed = regex.test(fileName); - } else { - // contains - passed = searchName.includes(searchPattern); - } - - return returnOutput({ passed }); -} - -export function actionAddToAlbum() { - const input = parseInput(); - const { authToken, config, data } = input; - const { albumId } = config; - - const ptr = Memory.fromString( - JSON.stringify({ - authToken, - assetId: data.asset.id, - albumId: albumId, - }), - ); - - addAssetToAlbum(ptr.offset); - ptr.free(); - - return returnOutput({ success: true }); -} - -export function actionArchive() { - const input = parseInput(); - const { authToken, data } = input; - const ptr = Memory.fromString( - JSON.stringify({ - authToken, - id: data.asset.id, - visibility: 'archive', - }), - ); - - updateAsset(ptr.offset); - ptr.free(); - - return returnOutput({ success: true }); -} diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 926a30ba88..2ef3fac60c 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -24,7 +24,7 @@ "@oazapfts/runtime": "^1.0.2" }, "devDependencies": { - "@types/node": "^24.12.2", + "@types/node": "^24.12.4", "typescript": "^6.0.0" } } diff --git a/packages/sdk/src/fetch-client.ts b/packages/sdk/src/fetch-client.ts index 6157154bec..e82074d02c 100644 --- a/packages/sdk/src/fetch-client.ts +++ b/packages/sdk/src/fetch-client.ts @@ -1478,72 +1478,33 @@ export type PersonStatisticsResponseDto = { /** Number of assets */ assets: number; }; -export type PluginJsonSchemaProperty = { - additionalProperties?: boolean | PluginJsonSchemaProperty; - "default"?: any; - description?: string; - "enum"?: string[]; - items?: PluginJsonSchemaProperty; - properties?: { - [key: string]: PluginJsonSchemaProperty; - }; - required?: string[]; - "type"?: PluginJsonSchemaType; -}; -export type PluginJsonSchema = { - additionalProperties?: boolean; - description?: string; - properties?: { - [key: string]: PluginJsonSchemaProperty; - }; - required?: string[]; - "type"?: PluginJsonSchemaType; -}; -export type PluginActionResponseDto = { - /** Action description */ +export type PluginMethodResponseDto = { + /** Description */ description: string; - /** Action ID */ - id: string; - /** Method name */ - methodName: string; - /** Plugin ID */ - pluginId: string; - /** Action schema */ - schema: (PluginJsonSchema) | null; - /** Supported contexts */ - supportedContexts: PluginContextType[]; - /** Action title */ - title: string; -}; -export type PluginFilterResponseDto = { - /** Filter description */ - description: string; - /** Filter ID */ - id: string; - /** Method name */ - methodName: string; - /** Plugin ID */ - pluginId: string; - /** Filter schema */ - schema: (PluginJsonSchema) | null; - /** Supported contexts */ - supportedContexts: PluginContextType[]; - /** Filter title */ + hostFunctions: boolean; + /** Key */ + key: string; + /** Name */ + name: string; + schema?: {}; + /** Title */ title: string; + /** Workflow types */ + types: WorkflowType[]; + /** Ui hints */ + uiHints: string[]; }; export type PluginResponseDto = { - /** Plugin actions */ - actions: PluginActionResponseDto[]; /** Plugin author */ author: string; /** Creation date */ createdAt: string; /** Plugin description */ description: string; - /** Plugin filters */ - filters: PluginFilterResponseDto[]; /** Plugin ID */ id: string; + /** Plugin methods */ + methods: PluginMethodResponseDto[]; /** Plugin name */ name: string; /** Plugin title */ @@ -1553,10 +1514,6 @@ export type PluginResponseDto = { /** Plugin version */ version: string; }; -export type PluginTriggerResponseDto = { - contextType: PluginContextType; - "type": PluginTriggerType; -}; export type QueueResponseDto = { /** Whether the queue is paused */ isPaused: boolean; @@ -2710,89 +2667,81 @@ export type CreateProfileImageResponseDto = { /** User ID */ userId: string; }; -export type PluginConfigValue = any; -export type WorkflowActionConfig = { - [key: string]: PluginConfigValue; -}; -export type WorkflowActionResponseDto = { - actionConfig: (WorkflowActionConfig) | null; - /** Action ID */ - id: string; - /** Action order */ - order: number; - /** Plugin action ID */ - pluginActionId: string; - /** Workflow ID */ - workflowId: string; -}; -export type WorkflowFilterConfig = { - [key: string]: PluginConfigValue; -}; -export type WorkflowFilterResponseDto = { - filterConfig: (WorkflowFilterConfig) | null; - /** Filter ID */ - id: string; - /** Filter order */ - order: number; - /** Plugin filter ID */ - pluginFilterId: string; - /** Workflow ID */ - workflowId: string; +export type WorkflowStepDto = { + /** Step configuration */ + config: { + [key: string]: any; + } | null; + /** Step is enabled */ + enabled?: boolean; + /** Step plugin method */ + method: string; }; export type WorkflowResponseDto = { - /** Workflow actions */ - actions: WorkflowActionResponseDto[]; /** Creation date */ createdAt: string; /** Workflow description */ - description: string; + description: string | null; /** Workflow enabled */ enabled: boolean; - /** Workflow filters */ - filters: WorkflowFilterResponseDto[]; /** Workflow ID */ id: string; /** Workflow name */ name: string | null; - /** Owner user ID */ - ownerId: string; - triggerType: PluginTriggerType; -}; -export type WorkflowActionItemDto = { - actionConfig?: WorkflowActionConfig; - /** Plugin action ID */ - pluginActionId: string; -}; -export type WorkflowFilterItemDto = { - filterConfig?: WorkflowFilterConfig; - /** Plugin filter ID */ - pluginFilterId: string; + /** Workflow steps */ + steps: WorkflowStepDto[]; + /** Workflow trigger type */ + trigger: WorkflowTrigger; + /** Update date */ + updatedAt: string; }; export type WorkflowCreateDto = { - /** Workflow actions */ - actions: WorkflowActionItemDto[]; /** Workflow description */ - description?: string; + description?: string | null; /** Workflow enabled */ enabled?: boolean; - /** Workflow filters */ - filters: WorkflowFilterItemDto[]; /** Workflow name */ - name: string; - triggerType: PluginTriggerType; + name?: string | null; + steps?: WorkflowStepDto[]; + /** Workflow trigger type */ + trigger: WorkflowTrigger; +}; +export type WorkflowTriggerResponseDto = { + /** Trigger type */ + trigger: WorkflowTrigger; + /** Workflow types */ + types: WorkflowType[]; }; export type WorkflowUpdateDto = { - /** Workflow actions */ - actions?: WorkflowActionItemDto[]; /** Workflow description */ - description?: string; + description?: string | null; /** Workflow enabled */ enabled?: boolean; - /** Workflow filters */ - filters?: WorkflowFilterItemDto[]; /** Workflow name */ - name?: string; - triggerType?: PluginTriggerType; + name?: string | null; + steps?: WorkflowStepDto[]; + /** Workflow trigger type */ + trigger?: WorkflowTrigger; +}; +export type WorkflowShareStepDto = { + /** Step configuration */ + config: { + [key: string]: any; + } | null; + /** Step is enabled */ + enabled?: boolean; + /** Step plugin method */ + method: string; +}; +export type WorkflowShareResponseDto = { + /** Workflow description */ + description: string | null; + /** Workflow name */ + name: string | null; + /** Workflow steps */ + steps: WorkflowShareStepDto[]; + /** Workflow trigger type */ + trigger: WorkflowTrigger; }; export type LicenseResponseDto = UserLicense; export type SyncAckV1 = {}; @@ -5240,22 +5189,56 @@ export function getPersonThumbnail({ id }: { /** * List all plugins */ -export function getPlugins(opts?: Oazapfts.RequestOpts) { +export function searchPlugins({ description, enabled, id, name, title, version }: { + description?: string; + enabled?: boolean; + id?: string; + name?: string; + title?: string; + version?: string; +}, opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; data: PluginResponseDto[]; - }>("/plugins", { + }>(`/plugins${QS.query(QS.explode({ + description, + enabled, + id, + name, + title, + version + }))}`, { ...opts })); } /** - * List all plugin triggers + * Retrieve plugin methods */ -export function getPluginTriggers(opts?: Oazapfts.RequestOpts) { +export function searchPluginMethods({ description, enabled, id, name, pluginName, pluginVersion, title, trigger, $type }: { + description?: string; + enabled?: boolean; + id?: string; + name?: string; + pluginName?: string; + pluginVersion?: string; + title?: string; + trigger?: WorkflowTrigger; + $type?: WorkflowType; +}, opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; - data: PluginTriggerResponseDto[]; - }>("/plugins/triggers", { + data: PluginMethodResponseDto[]; + }>(`/plugins/methods${QS.query(QS.explode({ + description, + enabled, + id, + name, + pluginName, + pluginVersion, + title, + trigger, + "type": $type + }))}`, { ...opts })); } @@ -6631,11 +6614,23 @@ export function getUniqueOriginalPaths(opts?: Oazapfts.RequestOpts) { /** * List all workflows */ -export function getWorkflows(opts?: Oazapfts.RequestOpts) { +export function searchWorkflows({ description, enabled, id, name, trigger }: { + description?: string; + enabled?: boolean; + id?: string; + name?: string; + trigger?: WorkflowTrigger; +}, opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; data: WorkflowResponseDto[]; - }>("/workflows", { + }>(`/workflows${QS.query(QS.explode({ + description, + enabled, + id, + name, + trigger + }))}`, { ...opts })); } @@ -6654,6 +6649,17 @@ export function createWorkflow({ workflowCreateDto }: { body: workflowCreateDto }))); } +/** + * List all workflow triggers + */ +export function getWorkflowTriggers(opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchJson<{ + status: 200; + data: WorkflowTriggerResponseDto[]; + }>("/workflows/triggers", { + ...opts + })); +} /** * Delete a workflow */ @@ -6694,6 +6700,19 @@ export function updateWorkflow({ id, workflowUpdateDto }: { body: workflowUpdateDto }))); } +/** + * Retrieve a workflow + */ +export function getWorkflowForShare({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchJson<{ + status: 200; + data: WorkflowShareResponseDto; + }>(`/workflows/${encodeURIComponent(id)}/share`, { + ...opts + })); +} export enum ReactionLevel { Album = "album", Asset = "asset" @@ -7017,21 +7036,11 @@ export enum PartnerDirection { SharedBy = "shared-by", SharedWith = "shared-with" } -export enum PluginJsonSchemaType { - String = "string", - Number = "number", - Integer = "integer", - Boolean = "boolean", - Object = "object", - Array = "array", - Null = "null" +export enum WorkflowType { + AssetV1 = "AssetV1", + AssetPersonV1 = "AssetPersonV1" } -export enum PluginContextType { - Asset = "asset", - Album = "album", - Person = "person" -} -export enum PluginTriggerType { +export enum WorkflowTrigger { AssetCreate = "AssetCreate", PersonRecognized = "PersonRecognized" } @@ -7098,7 +7107,7 @@ export enum JobName { VersionCheck = "VersionCheck", OcrQueueAll = "OcrQueueAll", Ocr = "Ocr", - WorkflowRun = "WorkflowRun" + WorkflowAssetCreate = "WorkflowAssetCreate" } export enum SearchSuggestionType { Country = "country", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b405a1090d..91a94de23c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,16 +35,16 @@ importers: dependencies: '@docusaurus/core': specifier: ~3.10.0 - version: 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) + version: 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) '@docusaurus/preset-classic': specifier: ~3.10.0 - version: 3.10.0(@algolia/client-search@5.46.0)(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(search-insights@2.17.3)(typescript@6.0.3) + version: 3.10.1(@algolia/client-search@5.52.1)(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(@types/react@19.2.15)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(search-insights@2.17.3)(typescript@6.0.3) '@docusaurus/theme-common': specifier: ~3.10.0 - version: 3.10.0(@docusaurus/plugin-content-docs@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@docusaurus/theme-mermaid': specifier: ~3.10.0 - version: 3.10.0(@docusaurus/plugin-content-docs@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) + version: 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) '@mdi/js': specifier: ^7.3.67 version: 7.4.47 @@ -53,47 +53,47 @@ importers: version: 1.6.1 '@mdx-js/react': specifier: ^3.0.0 - version: 3.1.1(@types/react@19.2.14)(react@19.2.5) + version: 3.1.1(@types/react@19.2.15)(react@19.2.6) autoprefixer: specifier: ^10.4.17 - version: 10.5.0(postcss@8.5.12) + version: 10.5.0(postcss@8.5.15) docusaurus-lunr-search: specifier: ^3.3.2 - version: 3.6.0(@docusaurus/core@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 3.6.0(@docusaurus/core@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6) lunr: specifier: ^2.3.9 version: 2.3.9 postcss: specifier: ^8.4.25 - version: 8.5.12 + version: 8.5.15 prism-react-renderer: specifier: ^2.3.1 - version: 2.4.1(react@19.2.5) + version: 2.4.1(react@19.2.6) raw-loader: specifier: ^4.0.2 - version: 4.0.2(webpack@5.106.2) + version: 4.0.2(webpack@5.107.0(postcss@8.5.15)) react: specifier: ^19.0.0 - version: 19.2.5 + version: 19.2.6 react-dom: specifier: ^19.0.0 - version: 19.2.5(react@19.2.5) + version: 19.2.6(react@19.2.6) tailwindcss: specifier: ^3.2.4 - version: 3.4.19(tsx@4.21.0)(yaml@2.8.3) + version: 3.4.19(tsx@4.22.3)(yaml@2.9.0) url: specifier: ^0.11.0 version: 0.11.4 devDependencies: '@docusaurus/module-type-aliases': specifier: ~3.10.0 - version: 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@docusaurus/tsconfig': specifier: ^3.10.0 - version: 3.10.0 + version: 3.10.1 '@docusaurus/types': specifier: ^3.10.0 - version: 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) prettier: specifier: ^3.7.4 version: 3.8.3 @@ -105,7 +105,7 @@ importers: devDependencies: '@eslint/js': specifier: ^10.0.0 - version: 10.0.1(eslint@10.2.1(jiti@2.6.1)) + version: 10.0.1(eslint@10.4.0(jiti@2.7.0)) '@faker-js/faker': specifier: ^10.1.0 version: 10.4.0 @@ -120,7 +120,7 @@ importers: version: link:../packages/sdk '@playwright/test': specifier: ^1.44.1 - version: 1.59.1 + version: 1.60.0 '@socket.io/component-emitter': specifier: ^3.1.2 version: 3.1.2 @@ -128,8 +128,8 @@ importers: specifier: ^3.4.2 version: 3.7.1 '@types/node': - specifier: ^24.12.2 - version: 24.12.2 + specifier: ^24.12.4 + version: 24.12.4 '@types/pg': specifier: ^8.15.1 version: 8.20.0 @@ -144,28 +144,28 @@ importers: version: 17.4.2 eslint: specifier: ^10.0.0 - version: 10.2.1(jiti@2.6.1) + version: 10.4.0(jiti@2.7.0) eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@10.2.1(jiti@2.6.1)) + version: 10.1.8(eslint@10.4.0(jiti@2.7.0)) eslint-plugin-prettier: specifier: ^5.1.3 - version: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.2.1(jiti@2.6.1)))(eslint@10.2.1(jiti@2.6.1))(prettier@3.8.3) + version: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.4.0(jiti@2.7.0)))(eslint@10.4.0(jiti@2.7.0))(prettier@3.8.3) eslint-plugin-unicorn: specifier: ^64.0.0 - version: 64.0.0(eslint@10.2.1(jiti@2.6.1)) + version: 64.0.0(eslint@10.4.0(jiti@2.7.0)) exiftool-vendored: specifier: ^35.0.0 version: 35.20.0 globals: specifier: ^17.0.0 - version: 17.5.0 + version: 17.6.0 luxon: specifier: ^3.4.4 version: 3.7.2 pg: specifier: ^8.11.3 - version: 8.20.0 + version: 8.21.0 pngjs: specifier: ^7.0.0 version: 7.0.0 @@ -189,16 +189,16 @@ importers: version: 6.0.3 typescript-eslint: specifier: ^8.28.0 - version: 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + version: 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) utimes: specifier: ^5.2.1 - version: 5.2.1(encoding@0.1.13) + version: 5.2.1 vite-tsconfig-paths: specifier: ^6.1.1 - version: 6.1.1(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 6.1.1(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) vitest: specifier: ^4.0.0 - version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@3.2.3))(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@24.12.4)(@vitest/coverage-v8@4.1.7)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@3.2.3))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) packages/cli: dependencies: @@ -220,7 +220,7 @@ importers: devDependencies: '@eslint/js': specifier: ^10.0.0 - version: 10.0.1(eslint@10.2.1(jiti@2.6.1)) + version: 10.0.1(eslint@10.4.0(jiti@2.7.0)) '@immich/sdk': specifier: workspace:* version: link:../sdk @@ -240,11 +240,11 @@ importers: specifier: ^4.13.1 version: 4.13.4 '@types/node': - specifier: ^24.12.2 - version: 24.12.2 + specifier: ^24.12.4 + version: 24.12.4 '@vitest/coverage-v8': specifier: ^4.0.0 - version: 4.1.5(vitest@4.1.5) + version: 4.1.7(vitest@4.1.7) byte-size: specifier: ^9.0.0 version: 9.0.1 @@ -256,19 +256,19 @@ importers: version: 12.1.0 eslint: specifier: ^10.0.0 - version: 10.2.1(jiti@2.6.1) + version: 10.4.0(jiti@2.7.0) eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@10.2.1(jiti@2.6.1)) + version: 10.1.8(eslint@10.4.0(jiti@2.7.0)) eslint-plugin-prettier: specifier: ^5.1.3 - version: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.2.1(jiti@2.6.1)))(eslint@10.2.1(jiti@2.6.1))(prettier@3.8.3) + version: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.4.0(jiti@2.7.0)))(eslint@10.4.0(jiti@2.7.0))(prettier@3.8.3) eslint-plugin-unicorn: specifier: ^64.0.0 - version: 64.0.0(eslint@10.2.1(jiti@2.6.1)) + version: 64.0.0(eslint@10.4.0(jiti@2.7.0)) globals: specifier: ^17.0.0 - version: 17.5.0 + version: 17.6.0 mock-fs: specifier: ^5.2.0 version: 5.5.0 @@ -283,19 +283,19 @@ importers: version: 6.0.3 typescript-eslint: specifier: ^8.58.0 - version: 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + version: 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) vite: specifier: ^8.0.0 - version: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) vitest: specifier: ^4.0.0 - version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@3.2.3))(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@24.12.4)(@vitest/coverage-v8@4.1.7)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@3.2.3))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) vitest-fetch-mock: specifier: ^0.4.0 - version: 0.4.5(vitest@4.1.5) + version: 0.4.5(vitest@4.1.7) yaml: specifier: ^2.3.1 - version: 2.8.3 + version: 2.9.0 packages/e2e-auth-server: devDependencies: @@ -304,19 +304,22 @@ importers: version: 9.5.0 jose: specifier: ^6.0.0 - version: 6.2.2 + version: 6.2.3 oidc-provider: specifier: ^9.0.0 - version: 9.8.2 + version: 9.8.3 tsx: specifier: ^4.20.6 - version: 4.21.0 + version: 4.22.3 - packages/plugins: + packages/plugin-core: devDependencies: '@extism/js-pdk': specifier: ^1.0.1 version: 1.1.1 + '@immich/plugin-sdk': + specifier: workspace:* + version: link:../plugin-sdk esbuild: specifier: ^0.28.0 version: 0.28.0 @@ -324,6 +327,24 @@ importers: specifier: ^6.0.0 version: 6.0.3 + packages/plugin-sdk: + devDependencies: + '@extism/js-pdk': + specifier: ^1.1.1 + version: 1.1.1 + '@types/node': + specifier: ^24.12.4 + version: 24.12.4 + esbuild: + specifier: ^0.28.0 + version: 0.28.0 + tsc-alias: + specifier: ^1.8.16 + version: 1.8.17 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + packages/sdk: dependencies: '@oazapfts/runtime': @@ -331,8 +352,8 @@ importers: version: 1.2.0 devDependencies: '@types/node': - specifier: ^24.12.2 - version: 24.12.2 + specifier: ^24.12.4 + version: 24.12.4 typescript: specifier: ^6.0.0 version: 6.0.3 @@ -342,33 +363,36 @@ importers: '@extism/extism': specifier: 2.0.0-rc13 version: 2.0.0-rc13 + '@immich/plugin-sdk': + specifier: workspace:* + version: link:../packages/plugin-sdk '@immich/sql-tools': specifier: ^0.5.1 version: 0.5.2 '@nestjs/bullmq': specifier: ^11.0.1 - version: 11.0.4(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(bullmq@5.76.1) + version: 11.0.4(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(bullmq@5.76.10) '@nestjs/common': specifier: ^11.0.4 - version: 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + version: 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/core': specifier: ^11.0.4 - version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + version: 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/platform-express': specifier: ^11.0.4 - version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) + version: 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21) '@nestjs/platform-socket.io': specifier: ^11.0.4 - version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.19)(rxjs@7.8.2) + version: 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.21)(rxjs@7.8.2) '@nestjs/schedule': specifier: ^6.0.0 - version: 6.1.3(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) + version: 6.1.3(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21) '@nestjs/swagger': specifier: ^11.4.2 - version: 11.4.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(class-transformer@0.5.1)(reflect-metadata@0.2.2) + version: 11.4.3(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(reflect-metadata@0.2.2) '@nestjs/websockets': specifier: ^11.0.4 - version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + version: 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(@nestjs/platform-socket.io@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@opentelemetry/api': specifier: ^1.9.0 version: 1.9.1 @@ -376,20 +400,20 @@ importers: specifier: ^2.0.0 version: 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/exporter-prometheus': - specifier: ^0.217.0 - version: 0.217.0(@opentelemetry/api@1.9.1) + specifier: ^0.218.0 + version: 0.218.0(@opentelemetry/api@1.9.1) '@opentelemetry/instrumentation-http': - specifier: ^0.215.0 - version: 0.215.0(@opentelemetry/api@1.9.1) + specifier: ^0.218.0 + version: 0.218.0(@opentelemetry/api@1.9.1) '@opentelemetry/instrumentation-ioredis': - specifier: ^0.63.0 - version: 0.63.0(@opentelemetry/api@1.9.1) + specifier: ^0.66.0 + version: 0.66.0(@opentelemetry/api@1.9.1) '@opentelemetry/instrumentation-nestjs-core': - specifier: ^0.61.0 - version: 0.61.0(@opentelemetry/api@1.9.1) + specifier: ^0.64.0 + version: 0.64.0(@opentelemetry/api@1.9.1) '@opentelemetry/instrumentation-pg': - specifier: ^0.67.0 - version: 0.67.0(@opentelemetry/api@1.9.1) + specifier: ^0.70.0 + version: 0.70.0(@opentelemetry/api@1.9.1) '@opentelemetry/resources': specifier: ^2.0.1 version: 2.7.1(@opentelemetry/api@1.9.1) @@ -397,20 +421,20 @@ importers: specifier: ^2.0.1 version: 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-node': - specifier: ^0.217.0 - version: 0.217.0(@opentelemetry/api@1.9.1) + specifier: ^0.218.0 + version: 0.218.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': specifier: ^1.34.0 - version: 1.40.0 + version: 1.41.1 '@react-email/components': specifier: ^1.0.0 - version: 1.0.12(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 1.0.12(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@react-email/render': specifier: ^2.0.0 - version: 2.0.7(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 2.0.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@socket.io/redis-adapter': specifier: ^8.3.0 - version: 8.3.0(socket.io-adapter@2.5.6) + version: 8.3.0(socket.io-adapter@2.5.7) archiver: specifier: ^7.0.0 version: 7.0.1 @@ -425,7 +449,7 @@ importers: version: 2.2.2 bullmq: specifier: ^5.51.0 - version: 5.76.1 + version: 5.76.10 chokidar: specifier: ^4.0.3 version: 4.0.3 @@ -453,9 +477,12 @@ importers: fluent-ffmpeg: specifier: ^2.1.2 version: 2.1.3 + generic-pool: + specifier: ^3.9.0 + version: 3.9.0 geo-tz: specifier: ^8.0.0 - version: 8.1.6 + version: 8.1.7 handlebars: specifier: ^4.7.8 version: 4.7.9 @@ -470,7 +497,7 @@ importers: version: 5.10.1 jose: specifier: ^6.0.0 - version: 6.2.2 + version: 6.2.3 js-yaml: specifier: ^4.1.0 version: 4.1.1 @@ -491,34 +518,34 @@ importers: version: 3.7.2 mnemonist: specifier: ^0.40.3 - version: 0.40.3 + version: 0.40.4 multer: specifier: ^2.0.2 version: 2.1.1 nest-commander: specifier: ^3.16.0 - version: 3.20.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@types/inquirer@8.2.12)(@types/node@24.12.2)(typescript@6.0.3) + version: 3.20.1(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(@types/inquirer@8.2.12)(@types/node@24.12.4)(typescript@6.0.3) nestjs-cls: specifier: ^6.0.0 - version: 6.2.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + version: 6.2.0(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) nestjs-kysely: specifier: 3.1.2 - version: 3.1.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(kysely@0.28.17)(reflect-metadata@0.2.2) + version: 3.1.2(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(kysely@0.28.17)(reflect-metadata@0.2.2) nestjs-otel: specifier: ^8.0.0 - version: 8.0.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) + version: 8.0.3(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21) nestjs-zod: specifier: ^5.3.0 - version: 5.3.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/swagger@11.4.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(class-transformer@0.5.1)(reflect-metadata@0.2.2))(rxjs@7.8.2)(zod@4.3.6) + version: 5.4.0(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/swagger@11.4.3(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(reflect-metadata@0.2.2))(rxjs@7.8.2)(zod@4.3.6) nodemailer: specifier: ^8.0.0 - version: 8.0.5 + version: 8.0.7 openid-client: specifier: ^6.3.3 - version: 6.8.3 + version: 6.8.4 pg: specifier: ^8.11.3 - version: 8.20.0 + version: 8.21.0 picomatch: specifier: ^4.0.2 version: 4.0.4 @@ -527,10 +554,10 @@ importers: version: 3.4.9 react: specifier: ^19.0.0 - version: 19.2.5 + version: 19.2.6 react-dom: specifier: ^19.0.0 - version: 19.2.5(react@19.2.5) + version: 19.2.6(react@19.2.6) react-email: specifier: ^5.0.0 version: 5.2.11 @@ -545,7 +572,7 @@ importers: version: 1.6.4 semver: specifier: ^7.6.2 - version: 7.7.4 + version: 7.8.0 sharp: specifier: ^0.34.5 version: 0.34.5 @@ -557,7 +584,7 @@ importers: version: 4.8.3 tailwindcss-preset-email: specifier: ^1.4.0 - version: 1.4.1(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3)) + version: 1.4.1(tailwindcss@3.4.19(tsx@4.22.3)(yaml@2.9.0)) thumbhash: specifier: ^0.1.1 version: 0.1.1 @@ -574,24 +601,24 @@ importers: specifier: ^13.12.0 version: 13.15.35 zod: - specifier: ^4.3.6 + specifier: 4.3.6 version: 4.3.6 devDependencies: '@eslint/js': specifier: ^10.0.0 - version: 10.0.1(eslint@10.2.1(jiti@2.6.1)) + version: 10.0.1(eslint@10.4.0(jiti@2.7.0)) '@nestjs/cli': specifier: ^11.0.2 - version: 11.0.21(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@24.12.2)(esbuild@0.28.0)(prettier@3.8.3) + version: 11.0.21(@swc/core@1.15.33(@swc/helpers@0.5.21))(@types/node@24.12.4)(esbuild@0.28.0)(lightningcss@1.32.0)(prettier@3.8.3) '@nestjs/schematics': specifier: ^11.0.0 version: 11.1.0(chokidar@4.0.3)(prettier@3.8.3)(typescript@6.0.3) '@nestjs/testing': specifier: ^11.0.4 - version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-express@11.1.19) + version: 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(@nestjs/platform-express@11.1.21) '@swc/core': specifier: ^1.4.14 - version: 1.15.30(@swc/helpers@0.5.21) + version: 1.15.33(@swc/helpers@0.5.21) '@types/archiver': specifier: ^7.0.0 version: 7.0.0 @@ -635,8 +662,8 @@ importers: specifier: ^2.0.0 version: 2.1.0 '@types/node': - specifier: ^24.12.2 - version: 24.12.2 + specifier: ^24.12.4 + version: 24.12.4 '@types/nodemailer': specifier: ^8.0.0 version: 8.0.0 @@ -648,7 +675,7 @@ importers: version: 6.0.5 '@types/react': specifier: ^19.0.0 - version: 19.2.14 + version: 19.2.15 '@types/semver': specifier: ^7.5.8 version: 7.7.1 @@ -663,22 +690,22 @@ importers: version: 13.15.10 '@vitest/coverage-v8': specifier: ^3.0.0 - version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.12.2)(happy-dom@20.9.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@3.2.3))(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 3.2.4(vitest@3.2.4(@types/debug@4.1.13)(@types/node@24.12.4)(happy-dom@20.9.0)(jiti@2.7.0)(jsdom@26.1.0(canvas@3.2.3))(lightningcss@1.32.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) eslint: specifier: ^10.0.0 - version: 10.2.1(jiti@2.6.1) + version: 10.4.0(jiti@2.7.0) eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@10.2.1(jiti@2.6.1)) + version: 10.1.8(eslint@10.4.0(jiti@2.7.0)) eslint-plugin-prettier: specifier: ^5.1.3 - version: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.2.1(jiti@2.6.1)))(eslint@10.2.1(jiti@2.6.1))(prettier@3.8.3) + version: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.4.0(jiti@2.7.0)))(eslint@10.4.0(jiti@2.7.0))(prettier@3.8.3) eslint-plugin-unicorn: specifier: ^64.0.0 - version: 64.0.0(eslint@10.2.1(jiti@2.6.1)) + version: 64.0.0(eslint@10.4.0(jiti@2.7.0)) globals: specifier: ^17.0.0 - version: 17.5.0 + version: 17.6.0 mock-fs: specifier: ^5.2.0 version: 5.5.0 @@ -693,13 +720,13 @@ importers: version: 4.3.0(prettier@3.8.3)(typescript@6.0.3) sql-formatter: specifier: ^15.0.0 - version: 15.7.3 + version: 15.8.0 supertest: specifier: ^7.1.0 version: 7.2.2 tailwindcss: specifier: ^3.4.0 - version: 3.4.19(tsx@4.21.0)(yaml@2.8.3) + version: 3.4.19(tsx@4.22.3)(yaml@2.9.0) testcontainers: specifier: ^11.0.0 version: 11.14.0 @@ -708,22 +735,22 @@ importers: version: 6.0.3 typescript-eslint: specifier: ^8.28.0 - version: 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + version: 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) unplugin-swc: specifier: ^1.4.5 - version: 1.5.9(@swc/core@1.15.30(@swc/helpers@0.5.21))(rollup@4.55.1) + version: 1.5.9(@swc/core@1.15.33(@swc/helpers@0.5.21))(rollup@4.60.4) vite-tsconfig-paths: specifier: ^6.0.0 - version: 6.1.1(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 6.1.1(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) vitest: specifier: ^3.0.0 - version: 3.2.4(@types/debug@4.1.12)(@types/node@24.12.2)(happy-dom@20.9.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@3.2.3))(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + version: 3.2.4(@types/debug@4.1.13)(@types/node@24.12.4)(happy-dom@20.9.0)(jiti@2.7.0)(jsdom@26.1.0(canvas@3.2.3))(lightningcss@1.32.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) web: dependencies: '@formatjs/icu-messageformat-parser': specifier: ^3.0.0 - version: 3.5.4 + version: 3.5.9 '@immich/justified-layout-wasm': specifier: ^0.4.3 version: 0.4.3 @@ -732,7 +759,7 @@ importers: version: link:../packages/sdk '@immich/ui': specifier: ^0.77.0 - version: 0.77.3(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2) + version: 0.77.3(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4)) '@mapbox/mapbox-gl-rtl-text': specifier: 0.4.0 version: 0.4.0 @@ -768,13 +795,13 @@ importers: version: 0.42.0 '@zoom-image/svelte': specifier: ^0.3.0 - version: 0.3.9(svelte@5.55.2) + version: 0.3.9(svelte@5.55.8(@typescript-eslint/types@8.59.4)) dom-to-image: specifier: ^2.6.0 version: 2.6.0 fabric: specifier: ^7.0.0 - version: 7.3.1 + version: 7.4.0 geo-coordinates-parser: specifier: ^1.7.4 version: 1.7.4 @@ -789,7 +816,7 @@ importers: version: 20.9.0 intl-messageformat: specifier: ^11.0.0 - version: 11.2.1 + version: 11.2.6 justified-layout: specifier: ^4.1.0 version: 4.1.0 @@ -804,7 +831,7 @@ importers: version: 5.24.0 media-chrome: specifier: ^4.19.0 - version: 4.19.0(react@19.2.5) + version: 4.19.0(react@19.2.6) pmtiles: specifier: ^4.3.0 version: 4.4.1 @@ -813,7 +840,7 @@ importers: version: 1.5.4 simple-icons: specifier: ^16.0.0 - version: 16.17.0 + version: 16.20.0 socket.io-client: specifier: ~4.8.0 version: 4.8.3 @@ -822,25 +849,25 @@ importers: version: 5.2.2 svelte-i18n: specifier: ^4.0.1 - version: 4.0.1(svelte@5.55.2) + version: 4.0.1(svelte@5.55.8(@typescript-eslint/types@8.59.4)) svelte-jsoneditor: specifier: ^3.10.0 - version: 3.12.0(svelte@5.55.2) + version: 3.12.0(svelte@5.55.8(@typescript-eslint/types@8.59.4)) svelte-maplibre: specifier: ^1.2.5 - version: 1.3.0(svelte@5.55.2) + version: 1.3.0(svelte@5.55.8(@typescript-eslint/types@8.59.4)) svelte-persisted-store: specifier: ^0.12.0 - version: 0.12.0(svelte@5.55.2) + version: 0.12.0(svelte@5.55.8(@typescript-eslint/types@8.59.4)) tabbable: specifier: ^6.2.0 version: 6.4.0 tailwind-merge: specifier: ^3.5.0 - version: 3.5.0 + version: 3.6.0 tailwind-variants: specifier: ^3.2.2 - version: 3.2.2(tailwind-merge@3.5.0)(tailwindcss@4.2.4) + version: 3.2.2(tailwind-merge@3.6.0)(tailwindcss@4.3.0) thumbhash: specifier: ^0.1.1 version: 0.1.1 @@ -853,43 +880,43 @@ importers: devDependencies: '@eslint/js': specifier: ^10.0.0 - version: 10.0.1(eslint@10.2.1(jiti@2.6.1)) + version: 10.0.1(eslint@10.4.0(jiti@2.7.0)) '@faker-js/faker': specifier: ^10.0.0 version: 10.4.0 '@koddsson/eslint-plugin-tscompat': specifier: ^0.2.0 - version: 0.2.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + version: 0.2.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) '@socket.io/component-emitter': specifier: ^3.1.0 version: 3.1.2 '@sveltejs/adapter-static': specifier: ^3.0.8 - version: 3.0.10(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))) + version: 3.0.10(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0))) '@sveltejs/enhanced-img': specifier: ^0.10.4 - version: 0.10.4(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(rollup@4.55.1)(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 0.10.4(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(rollup@4.60.4)(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) '@sveltejs/kit': specifier: ^2.56.1 - version: 2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) '@sveltejs/vite-plugin-svelte': - specifier: 7.0.0 - version: 7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + specifier: 7.1.2 + version: 7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) '@tailwindcss/vite': specifier: ^4.2.4 - version: 4.2.4(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.3.0(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) '@testing-library/jest-dom': specifier: ^6.4.2 version: 6.9.1 '@testing-library/svelte': specifier: ^5.2.8 - version: 5.3.1(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5) + version: 5.3.1(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0))(vitest@4.1.7) '@testing-library/user-event': specifier: ^14.5.2 version: 14.6.1(@testing-library/dom@10.4.1) '@trivago/prettier-plugin-sort-imports': specifier: ^6.0.2 - version: 6.0.2(prettier-plugin-svelte@3.5.1(prettier@3.8.3)(svelte@5.55.2))(prettier@3.8.3)(svelte@5.55.2) + version: 6.0.2(prettier-plugin-svelte@3.5.2(prettier@3.8.3)(svelte@5.55.8(@typescript-eslint/types@8.59.4)))(prettier@3.8.3)(svelte@5.55.8(@typescript-eslint/types@8.59.4)) '@types/chromecast-caf-sender': specifier: ^1.0.11 version: 1.0.11 @@ -910,34 +937,34 @@ importers: version: 1.5.6 '@vitest/coverage-v8': specifier: ^4.0.0 - version: 4.1.5(vitest@4.1.5) + version: 4.1.7(vitest@4.1.7) dotenv: specifier: ^17.0.0 version: 17.4.2 eslint: specifier: ^10.2.1 - version: 10.2.1(jiti@2.6.1) + version: 10.4.0(jiti@2.7.0) eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@10.2.1(jiti@2.6.1)) + version: 10.1.8(eslint@10.4.0(jiti@2.7.0)) eslint-plugin-better-tailwindcss: specifier: ^4.5.0 - version: 4.5.0(eslint@10.2.1(jiti@2.6.1))(tailwindcss@4.2.4)(typescript@6.0.3) + version: 4.5.0(eslint@10.4.0(jiti@2.7.0))(tailwindcss@4.3.0)(typescript@6.0.3) eslint-plugin-compat: specifier: ^7.0.0 - version: 7.0.1(eslint@10.2.1(jiti@2.6.1)) + version: 7.0.2(eslint@10.4.0(jiti@2.7.0)) eslint-plugin-svelte: specifier: ^3.12.4 - version: 3.17.1(eslint@10.2.1(jiti@2.6.1))(svelte@5.55.2) + version: 3.17.1(eslint@10.4.0(jiti@2.7.0))(svelte@5.55.8(@typescript-eslint/types@8.59.4)) eslint-plugin-unicorn: specifier: ^64.0.0 - version: 64.0.0(eslint@10.2.1(jiti@2.6.1)) + version: 64.0.0(eslint@10.4.0(jiti@2.7.0)) factory.ts: specifier: ^1.4.1 version: 1.4.2 globals: specifier: ^17.0.0 - version: 17.5.0 + version: 17.6.0 prettier: specifier: ^3.7.4 version: 3.8.3 @@ -946,137 +973,125 @@ importers: version: 4.2.0(prettier@3.8.3) prettier-plugin-svelte: specifier: ^3.3.3 - version: 3.5.1(prettier@3.8.3)(svelte@5.55.2) + version: 3.5.2(prettier@3.8.3)(svelte@5.55.8(@typescript-eslint/types@8.59.4)) rollup-plugin-visualizer: specifier: ^7.0.0 - version: 7.0.1(rolldown@1.0.0-rc.17)(rollup@4.55.1) + version: 7.0.1(rolldown@1.0.1)(rollup@4.60.4) svelte: - specifier: 5.55.2 - version: 5.55.2 + specifier: 5.55.8 + version: 5.55.8(@typescript-eslint/types@8.59.4) svelte-check: specifier: ^4.4.6 - version: 4.4.6(picomatch@4.0.4)(svelte@5.55.2)(typescript@6.0.3) + version: 4.4.8(picomatch@4.0.4)(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3) svelte-eslint-parser: specifier: ^1.3.3 - version: 1.6.0(svelte@5.55.2) + version: 1.6.1(svelte@5.55.8(@typescript-eslint/types@8.59.4)) tailwindcss: specifier: ^4.2.4 - version: 4.2.4 + version: 4.3.0 typescript: specifier: ^6.0.0 version: 6.0.3 typescript-eslint: specifier: ^8.45.0 - version: 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + version: 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) vite: specifier: ^8.0.0 - version: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) vitest: specifier: ^4.0.0 - version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@3.2.3))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@24.12.4)(@vitest/coverage-v8@4.1.7)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@3.2.3))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) packages: '@adobe/css-tools@4.4.4': resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} - '@ai-sdk/gateway@2.0.21': - resolution: {integrity: sha512-BwV7DU/lAm3Xn6iyyvZdWgVxgLu3SNXzl5y57gMvkW4nGhAOV5269IrJzQwGt03bb107sa6H6uJwWxc77zXoGA==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - - '@ai-sdk/provider-utils@3.0.19': - resolution: {integrity: sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - - '@ai-sdk/provider@2.0.0': - resolution: {integrity: sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==} - engines: {node: '>=18'} - - '@ai-sdk/react@2.0.115': - resolution: {integrity: sha512-Etu7gWSEi2dmXss1PoR5CAZGwGShXsF9+Pon1eRO6EmatjYaBMhq1CfHPyYhGzWrint8jJIK2VaAhiMef29qZw==} - engines: {node: '>=18'} - peerDependencies: - react: ^18 || ~19.0.1 || ~19.1.2 || ^19.2.1 - zod: ^3.25.76 || ^4.1.8 - peerDependenciesMeta: - zod: - optional: true - - '@algolia/abtesting@1.12.0': - resolution: {integrity: sha512-EfW0bfxjPs+C7ANkJDw2TATntfBKsFiy7APh+KO0pQ8A6HYa5I0NjFuCGCXWfzzzLXNZta3QUl3n5Kmm6aJo9Q==} + '@algolia/abtesting@1.18.1': + resolution: {integrity: sha512-aehCadlWOGvrT91KUIZpC0MbB8KBW9yUuvTJFd2xesR7le/IsT4nJUnjCCZ4ZqZCeTcPHPV5mo//fZ5oxcSVYw==} engines: {node: '>= 14.0.0'} '@algolia/autocomplete-core@1.19.2': resolution: {integrity: sha512-mKv7RyuAzXvwmq+0XRK8HqZXt9iZ5Kkm2huLjgn5JoCPtDy+oh9yxUMfDDaVCw0oyzZ1isdJBc7l9nuCyyR7Nw==} + '@algolia/autocomplete-core@1.19.8': + resolution: {integrity: sha512-3YEorYg44niXcm7gkft3nXYItHd44e8tmh4D33CTszPgP0QWkaLEaFywiNyJBo7UL/mqObA/G9RYuU7R8tN1IA==} + '@algolia/autocomplete-plugin-algolia-insights@1.19.2': resolution: {integrity: sha512-TjxbcC/r4vwmnZaPwrHtkXNeqvlpdyR+oR9Wi2XyfORkiGkLTVhX2j+O9SaCCINbKoDfc+c2PB8NjfOnz7+oKg==} peerDependencies: search-insights: '>= 1 < 3' + '@algolia/autocomplete-plugin-algolia-insights@1.19.8': + resolution: {integrity: sha512-ZvJWO8ZZJDpc1LNM2TTBdmQsZBLMR4rU5iNR2OYvEeFBiaf/0ESnRSSLQbryarJY4SVxtoz6A2ZtDMNM+iQEAA==} + peerDependencies: + search-insights: '>= 1 < 3' + '@algolia/autocomplete-shared@1.19.2': resolution: {integrity: sha512-jEazxZTVD2nLrC+wYlVHQgpBoBB5KPStrJxLzsIFl6Kqd1AlG9sIAGl39V5tECLpIQzB3Qa2T6ZPJ1ChkwMK/w==} peerDependencies: '@algolia/client-search': '>= 4.9.1 < 6' algoliasearch: '>= 4.9.1 < 6' - '@algolia/client-abtesting@5.46.0': - resolution: {integrity: sha512-eG5xV8rujK4ZIHXrRshvv9O13NmU/k42Rnd3w43iKH5RaQ2zWuZO6Q7XjaoJjAFVCsJWqRbXzbYyPGrbF3wGNg==} + '@algolia/autocomplete-shared@1.19.8': + resolution: {integrity: sha512-h5hf2t8ejF6vlOgvLaZzQbWs5SyH2z4PAWygNAvvD/2RI29hdQ54ldUGwqVuj9Srs+n8XUKTPUqb7fvhBhQrnQ==} + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' + + '@algolia/client-abtesting@5.52.1': + resolution: {integrity: sha512-HmXOGBOAOJPounpBzBpuY0zDYeiCpxgHnQmuA7JO6ScukcBdGp3/XM9zJk5pJx/xNGD68mbPGXWpDxGtl6BwDQ==} engines: {node: '>= 14.0.0'} - '@algolia/client-analytics@5.46.0': - resolution: {integrity: sha512-AYh2uL8IUW9eZrbbT+wZElyb7QkkeV3US2NEKY7doqMlyPWE8lErNfkVN1NvZdVcY4/SVic5GDbeDz2ft8YIiQ==} + '@algolia/client-analytics@5.52.1': + resolution: {integrity: sha512-5oo4+I8iixie9vXhCyNFCzeIr8pqA3FQ//VsLHTDvZAV4ttYOPGvYHGQq5NSalrLx5Jc3dRro/5uDOlnUMcBJg==} engines: {node: '>= 14.0.0'} - '@algolia/client-common@5.46.0': - resolution: {integrity: sha512-0emZTaYOeI9WzJi0TcNd2k3SxiN6DZfdWc2x2gHt855Jl9jPUOzfVTL6gTvCCrOlT4McvpDGg5nGO+9doEjjig==} + '@algolia/client-common@5.52.1': + resolution: {integrity: sha512-qCDoZfx5MpX7XQzvQ3bC4tSEMkQWQMaF/ABtLuoze03Y/flR563CCSws02qIJ23oX7lxl92LsilZjINVyTdtLw==} engines: {node: '>= 14.0.0'} - '@algolia/client-insights@5.46.0': - resolution: {integrity: sha512-wrBJ8fE+M0TDG1As4DDmwPn2TXajrvmvAN72Qwpuv8e2JOKNohF7+JxBoF70ZLlvP1A1EiH8DBu+JpfhBbNphQ==} + '@algolia/client-insights@5.52.1': + resolution: {integrity: sha512-hnGs0/lsFJ2PWDxNBz7pxreXo/Xz7gxYRcfePBUjsH26ad0kU/sgnVZd9LwWBpsQv65z2jlb5dkyaB9WE9M9FQ==} engines: {node: '>= 14.0.0'} - '@algolia/client-personalization@5.46.0': - resolution: {integrity: sha512-LnkeX4p0ENt0DoftDJJDzQQJig/sFQmD1eQifl/iSjhUOGUIKC/7VTeXRcKtQB78naS8njUAwpzFvxy1CDDXDQ==} + '@algolia/client-personalization@5.52.1': + resolution: {integrity: sha512-2VxxNc/uBysyKvGeBdSM5n9eIDKH8kWD7wd9/yqbJAiVwU4Yv6tU1LSJusHKrXV/aCu1KW7t9Gug9QyeEmtn/Q==} engines: {node: '>= 14.0.0'} - '@algolia/client-query-suggestions@5.46.0': - resolution: {integrity: sha512-aF9tc4ex/smypXw+W3lBPB1jjKoaGHpZezTqofvDOI/oK1dR2sdTpFpK2Ru+7IRzYgwtRqHF3znmTlyoNs9dpA==} + '@algolia/client-query-suggestions@5.52.1': + resolution: {integrity: sha512-O6mPtsw3xEfNOe6gWFpYLeAZAIljNa4Hgna3bq15PwyN7nbjTY0wXJFRbzs/0YVf75Br+SbOQUmjKxXYjDiSiQ==} engines: {node: '>= 14.0.0'} - '@algolia/client-search@5.46.0': - resolution: {integrity: sha512-22SHEEVNjZfFWkFks3P6HilkR3rS7a6GjnCIqR22Zz4HNxdfT0FG+RE7efTcFVfLUkTTMQQybvaUcwMrHXYa7Q==} + '@algolia/client-search@5.52.1': + resolution: {integrity: sha512-gA8oJOV1LnQQkDf91iebNnFInHuW0gRPEgLSOQ7EfipCEjYTHm5swm1DlH9H5RaRw4RrHuzHBegnlzc0MAstcg==} engines: {node: '>= 14.0.0'} '@algolia/events@4.0.1': resolution: {integrity: sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==} - '@algolia/ingestion@1.46.0': - resolution: {integrity: sha512-2LT0/Z+/sFwEpZLH6V17WSZ81JX2uPjgvv5eNlxgU7rPyup4NXXfuMbtCJ+6uc4RO/LQpEJd3Li59ke3wtyAsA==} + '@algolia/ingestion@1.52.1': + resolution: {integrity: sha512-U9zZfc5xIu9wRxZkt+HceJUAD4VKHKbAyLSloJdEyMRmphXeibfrY9cxqIXBcmPeZzGhn3Imb35Dq8l19PkJhw==} engines: {node: '>= 14.0.0'} - '@algolia/monitoring@1.46.0': - resolution: {integrity: sha512-uivZ9wSWZ8mz2ZU0dgDvQwvVZV8XBv6lYBXf8UtkQF3u7WeTqBPeU8ZoeTyLpf0jAXCYOvc1mAVmK0xPLuEwOQ==} + '@algolia/monitoring@1.52.1': + resolution: {integrity: sha512-a3SGNceHmkQfq77iG8Ka+w1pvwfZa/0lzEIgse30fL0kD+yKnd/dg0dQvSfFPAEt2f21DMcGkDSSeJlO3KdQjQ==} engines: {node: '>= 14.0.0'} - '@algolia/recommend@5.46.0': - resolution: {integrity: sha512-O2BB8DuySuddgOAbhyH4jsGbL+KyDGpzJRtkDZkv091OMomqIA78emhhMhX9d/nIRrzS1wNLWB/ix7Hb2eV5rg==} + '@algolia/recommend@5.52.1': + resolution: {integrity: sha512-z98QEguCFDpxb4S/PyrUK1igqF8tPsdbqOUUO6ON91vJ58w+Gwa6ncrI0oNXSFcrkxA5EqPKPQ2A1PBCn08TYQ==} engines: {node: '>= 14.0.0'} - '@algolia/requester-browser-xhr@5.46.0': - resolution: {integrity: sha512-eW6xyHCyYrJD0Kjk9Mz33gQ40LfWiEA51JJTVfJy3yeoRSw/NXhAL81Pljpa0qslTs6+LO/5DYPZddct6HvISQ==} + '@algolia/requester-browser-xhr@5.52.1': + resolution: {integrity: sha512-CI7+/0I11QeZM59Uc8whd2or0kqzFVjpaPn9Qpwll/krHcBAxk24WkAQ6WX+IwDVMfpont4YGbKwAmCre3vE8Q==} engines: {node: '>= 14.0.0'} - '@algolia/requester-fetch@5.46.0': - resolution: {integrity: sha512-Vn2+TukMGHy4PIxmdvP667tN/MhS7MPT8EEvEhS6JyFLPx3weLcxSa1F9gVvrfHWCUJhLWoMVJVB2PT8YfRGcw==} + '@algolia/requester-fetch@5.52.1': + resolution: {integrity: sha512-S6bDuw9byfOvm3T71cgdoZgrgnZq6hpdMLkx52Louh57nUAmvGQESz2aojOynQHjbTiV55smvAFbgn0qT4tJrg==} engines: {node: '>= 14.0.0'} - '@algolia/requester-node-http@5.46.0': - resolution: {integrity: sha512-xaqXyna5yBZ+r1SJ9my/DM6vfTqJg9FJgVydRJ0lnO+D5NhqGW/qaRG/iBGKr/d4fho34el6WakV7BqJvrl/HQ==} + '@algolia/requester-node-http@5.52.1': + resolution: {integrity: sha512-tqZXM+54rWo4mk5jL5Z/flE11nPmNEdXwFBM5py9DkOmbjeCNemfVd45FyM97XdzfZ0dl9uOJC6PYn1FpkeyQg==} engines: {node: '>= 14.0.0'} '@alloc/quick-lru@5.2.0': @@ -1115,12 +1130,12 @@ packages: resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.5': - resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} + '@babel/compat-data@7.29.3': + resolution: {integrity: sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==} engines: {node: '>=6.9.0'} - '@babel/core@7.28.5': - resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} engines: {node: '>=6.9.0'} '@babel/generator@7.29.1': @@ -1131,12 +1146,12 @@ packages: resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.27.2': - resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} engines: {node: '>=6.9.0'} - '@babel/helper-create-class-features-plugin@7.28.5': - resolution: {integrity: sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==} + '@babel/helper-create-class-features-plugin@7.29.3': + resolution: {integrity: sha512-RpLYy2sb51oNLjuu1iD3bwBqCBWUzjO0ocp+iaCP/lJtb2CPLcnC2Fftw+4sAzaMELGeWTgExSKADbdo0GFVzA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1147,8 +1162,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-define-polyfill-provider@0.6.5': - resolution: {integrity: sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==} + '@babel/helper-define-polyfill-provider@0.6.8': + resolution: {integrity: sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 @@ -1160,12 +1175,12 @@ packages: resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.27.1': - resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.28.3': - resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1174,8 +1189,8 @@ packages: resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} engines: {node: '>=6.9.0'} - '@babel/helper-plugin-utils@7.27.1': - resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} engines: {node: '>=6.9.0'} '@babel/helper-remap-async-to-generator@7.27.1': @@ -1184,8 +1199,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-replace-supers@7.27.1': - resolution: {integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==} + '@babel/helper-replace-supers@7.28.6': + resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1206,12 +1221,12 @@ packages: resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} - '@babel/helper-wrap-function@7.28.3': - resolution: {integrity: sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==} + '@babel/helper-wrap-function@7.28.6': + resolution: {integrity: sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.28.4': - resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} + '@babel/helpers@7.29.2': + resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} engines: {node: '>=6.9.0'} '@babel/parser@7.27.0': @@ -1219,8 +1234,8 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@7.29.2': - resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} + '@babel/parser@7.29.3': + resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==} engines: {node: '>=6.0.0'} hasBin: true @@ -1242,14 +1257,20 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.3': + resolution: {integrity: sha512-SRS46DFR4HqzUzCVgi90/xMoL+zeBDBvWdKYXSEzh79kXswNFEglUpMKxR04//dPqwYXWUBJ3mpUd933ru9Kmg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1': resolution: {integrity: sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.13.0 - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.3': - resolution: {integrity: sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==} + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6': + resolution: {integrity: sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1265,26 +1286,26 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-import-assertions@7.27.1': - resolution: {integrity: sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==} + '@babel/plugin-syntax-import-assertions@7.28.6': + resolution: {integrity: sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-import-attributes@7.27.1': - resolution: {integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==} + '@babel/plugin-syntax-import-attributes@7.28.6': + resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-jsx@7.27.1': - resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} + '@babel/plugin-syntax-jsx@7.28.6': + resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-typescript@7.27.1': - resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} + '@babel/plugin-syntax-typescript@7.28.6': + resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1301,14 +1322,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-async-generator-functions@7.28.0': - resolution: {integrity: sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==} + '@babel/plugin-transform-async-generator-functions@7.29.0': + resolution: {integrity: sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-async-to-generator@7.27.1': - resolution: {integrity: sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==} + '@babel/plugin-transform-async-to-generator@7.28.6': + resolution: {integrity: sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1319,32 +1340,32 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-block-scoping@7.28.5': - resolution: {integrity: sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==} + '@babel/plugin-transform-block-scoping@7.28.6': + resolution: {integrity: sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-class-properties@7.27.1': - resolution: {integrity: sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==} + '@babel/plugin-transform-class-properties@7.28.6': + resolution: {integrity: sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-class-static-block@7.28.3': - resolution: {integrity: sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==} + '@babel/plugin-transform-class-static-block@7.28.6': + resolution: {integrity: sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.12.0 - '@babel/plugin-transform-classes@7.28.4': - resolution: {integrity: sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==} + '@babel/plugin-transform-classes@7.28.6': + resolution: {integrity: sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-computed-properties@7.27.1': - resolution: {integrity: sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==} + '@babel/plugin-transform-computed-properties@7.28.6': + resolution: {integrity: sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1355,8 +1376,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-dotall-regex@7.27.1': - resolution: {integrity: sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==} + '@babel/plugin-transform-dotall-regex@7.28.6': + resolution: {integrity: sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1367,8 +1388,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1': - resolution: {integrity: sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==} + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0': + resolution: {integrity: sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1379,14 +1400,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-explicit-resource-management@7.28.0': - resolution: {integrity: sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==} + '@babel/plugin-transform-explicit-resource-management@7.28.6': + resolution: {integrity: sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-exponentiation-operator@7.28.5': - resolution: {integrity: sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==} + '@babel/plugin-transform-exponentiation-operator@7.28.6': + resolution: {integrity: sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1409,8 +1430,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-json-strings@7.27.1': - resolution: {integrity: sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==} + '@babel/plugin-transform-json-strings@7.28.6': + resolution: {integrity: sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1421,8 +1442,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-logical-assignment-operators@7.28.5': - resolution: {integrity: sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==} + '@babel/plugin-transform-logical-assignment-operators@7.28.6': + resolution: {integrity: sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1439,14 +1460,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-commonjs@7.27.1': - resolution: {integrity: sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==} + '@babel/plugin-transform-modules-commonjs@7.28.6': + resolution: {integrity: sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-systemjs@7.28.5': - resolution: {integrity: sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==} + '@babel/plugin-transform-modules-systemjs@7.29.4': + resolution: {integrity: sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1457,8 +1478,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-named-capturing-groups-regex@7.27.1': - resolution: {integrity: sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==} + '@babel/plugin-transform-named-capturing-groups-regex@7.29.0': + resolution: {integrity: sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1469,20 +1490,20 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-nullish-coalescing-operator@7.27.1': - resolution: {integrity: sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==} + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6': + resolution: {integrity: sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-numeric-separator@7.27.1': - resolution: {integrity: sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==} + '@babel/plugin-transform-numeric-separator@7.28.6': + resolution: {integrity: sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-object-rest-spread@7.28.4': - resolution: {integrity: sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==} + '@babel/plugin-transform-object-rest-spread@7.28.6': + resolution: {integrity: sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1493,14 +1514,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-optional-catch-binding@7.27.1': - resolution: {integrity: sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==} + '@babel/plugin-transform-optional-catch-binding@7.28.6': + resolution: {integrity: sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-optional-chaining@7.28.5': - resolution: {integrity: sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==} + '@babel/plugin-transform-optional-chaining@7.28.6': + resolution: {integrity: sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1511,14 +1532,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-private-methods@7.27.1': - resolution: {integrity: sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==} + '@babel/plugin-transform-private-methods@7.28.6': + resolution: {integrity: sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-private-property-in-object@7.27.1': - resolution: {integrity: sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==} + '@babel/plugin-transform-private-property-in-object@7.28.6': + resolution: {integrity: sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1547,8 +1568,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx@7.27.1': - resolution: {integrity: sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==} + '@babel/plugin-transform-react-jsx@7.28.6': + resolution: {integrity: sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1559,14 +1580,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-regenerator@7.28.4': - resolution: {integrity: sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==} + '@babel/plugin-transform-regenerator@7.29.0': + resolution: {integrity: sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-regexp-modifiers@7.27.1': - resolution: {integrity: sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==} + '@babel/plugin-transform-regexp-modifiers@7.28.6': + resolution: {integrity: sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1577,8 +1598,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-runtime@7.28.5': - resolution: {integrity: sha512-20NUVgOrinudkIBzQ2bNxP08YpKprUkRTiRSd2/Z5GOdPImJGkoN4Z7IQe1T5AdyKI1i5L6RBmluqdSzvaq9/w==} + '@babel/plugin-transform-runtime@7.29.0': + resolution: {integrity: sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1589,8 +1610,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-spread@7.27.1': - resolution: {integrity: sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==} + '@babel/plugin-transform-spread@7.28.6': + resolution: {integrity: sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1613,8 +1634,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-typescript@7.28.5': - resolution: {integrity: sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA==} + '@babel/plugin-transform-typescript@7.28.6': + resolution: {integrity: sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1625,8 +1646,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-unicode-property-regex@7.27.1': - resolution: {integrity: sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==} + '@babel/plugin-transform-unicode-property-regex@7.28.6': + resolution: {integrity: sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1637,14 +1658,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-unicode-sets-regex@7.27.1': - resolution: {integrity: sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==} + '@babel/plugin-transform-unicode-sets-regex@7.28.6': + resolution: {integrity: sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/preset-env@7.28.5': - resolution: {integrity: sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==} + '@babel/preset-env@7.29.5': + resolution: {integrity: sha512-/69t2aEzGKHD76DyLbHysF/QH2LJOB8iFnYO37unDTKBTubzcMRv0f3H5EiN1Q6ajOd/eB7dAInF0qdFVS06kA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1696,26 +1717,14 @@ packages: '@borewit/text-codec@0.2.2': resolution: {integrity: sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==} - '@braintree/sanitize-url@7.1.1': - resolution: {integrity: sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==} + '@braintree/sanitize-url@7.1.2': + resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==} - '@chevrotain/cst-dts-gen@11.0.3': - resolution: {integrity: sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==} + '@chevrotain/types@11.1.2': + resolution: {integrity: sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==} - '@chevrotain/gast@11.0.3': - resolution: {integrity: sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==} - - '@chevrotain/regexp-to-ast@11.0.3': - resolution: {integrity: sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==} - - '@chevrotain/types@11.0.3': - resolution: {integrity: sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==} - - '@chevrotain/utils@11.0.3': - resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==} - - '@codemirror/autocomplete@6.20.1': - resolution: {integrity: sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A==} + '@codemirror/autocomplete@6.20.2': + resolution: {integrity: sha512-G5FPkgIiLjOgZMjqVjvuKQ1rGPtHogLldJr33eFJdVLtmwY+giGrlv/ewljLz6b9BSQLkjxuwBc6g6omDM+YxQ==} '@codemirror/commands@6.10.3': resolution: {integrity: sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q==} @@ -1726,8 +1735,8 @@ packages: '@codemirror/language@6.12.3': resolution: {integrity: sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==} - '@codemirror/lint@6.9.5': - resolution: {integrity: sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA==} + '@codemirror/lint@6.9.6': + resolution: {integrity: sha512-6Kp7r6XfCi/D/5sdXieMfg9pJU1bUEx96WITuLU6ESaKizCz0QHFMjY/TaFSbigDdEAIgi93itLBIUETP4oK+A==} '@codemirror/search@6.7.0': resolution: {integrity: sha512-ZvGm99wc/s2cITtMT15LFdn8aH/aS+V+DqyGq/N5ZlV5vWtH+nILvC2nw0zX7ByNoHHDZ2IxxdW38O0tc5nVHg==} @@ -1735,8 +1744,8 @@ packages: '@codemirror/state@6.6.0': resolution: {integrity: sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==} - '@codemirror/view@6.41.1': - resolution: {integrity: sha512-ToDnWKbBnke+ZLrP6vgTTDScGi5H37YYuZGniQaBzxMVdtCxMrslsmtnOvbPZk4RX9bvkQqnWR/WS/35tJA0qg==} + '@codemirror/view@6.43.0': + resolution: {integrity: sha512-V7ZCLQO3Jus9hzh2jVCCPW3mO4IBMr43O37PqSUYautJSnnJF41YlgLw21x0fLJTYvJ+Vkm6Gp+qKGH9pltgXA==} '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} @@ -1934,8 +1943,8 @@ packages: peerDependencies: postcss: ^8.4 - '@csstools/postcss-normalize-display-values@4.0.0': - resolution: {integrity: sha512-HlEoG0IDRoHXzXnkV4in47dzsxdsjdz6+j7MLjaACABX2NfvjFS6XVAnpaDyGesz9gK2SC7MbNwdCHusObKJ9Q==} + '@csstools/postcss-normalize-display-values@4.0.1': + resolution: {integrity: sha512-TQUGBuRvxdc7TgNSTevYqrL8oItxiwPDixk20qCB5me/W8uF7BPbhRrAvFuhEoywQp/woRsUZ6SJ+sU5idZAIA==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -1958,6 +1967,12 @@ packages: peerDependencies: postcss: ^8.4 + '@csstools/postcss-property-rule-prelude-list@1.0.0': + resolution: {integrity: sha512-IxuQjUXq19fobgmSSvUDO7fVwijDJaZMvWQugxfEUxmjBeDCVaDuMpsZ31MsTm5xbnhA+ElDi0+rQ7sQQGisFA==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + '@csstools/postcss-random-function@2.0.1': resolution: {integrity: sha512-q+FQaNiRBhnoSNo+GzqGOIBKoHQ43lYz0ICrV+UudfWnEF6ksS6DsBIJSISKQT2Bvu3g4k6r7t0zYrk5pDlo8w==} engines: {node: '>=18'} @@ -1988,6 +2003,12 @@ packages: peerDependencies: postcss: ^8.4 + '@csstools/postcss-syntax-descriptor-syntax-production@1.0.1': + resolution: {integrity: sha512-GneqQWefjM//f4hJ/Kbox0C6f2T7+pi4/fqTqOFGTL3EjnvOReTqO1qUQ30CaUjkwjYq9qZ41hzarrAxCc4gow==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + '@csstools/postcss-system-ui-font-family@1.0.0': resolution: {integrity: sha512-s3xdBvfWYfoPSBsikDXbuorcMG1nN1M6GdU0qBsGfcmNR0A/qhloQZpTxjA3Xsyrk1VJvwb2pOfiOT3at/DuIQ==} engines: {node: '>=18'} @@ -2034,8 +2055,8 @@ packages: resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} engines: {node: '>=10.0.0'} - '@docsearch/core@4.3.1': - resolution: {integrity: sha512-ktVbkePE+2h9RwqCUMbWXOoebFyDOxHqImAqfs+lC8yOU+XwEW4jgvHGJK079deTeHtdhUNj0PXHSnhJINvHzQ==} + '@docsearch/core@4.6.3': + resolution: {integrity: sha512-rUOujwIpxJRgD7+kicVsI3D5sqBvdiRTquzWBpTEXZs8ZXfGbfzpus5HqumaNYTppN2HvH8E2yNuRwYdHJeOlA==} peerDependencies: '@types/react': '>= 16.8.0 < 20.0.0' react: '>= 16.8.0 < 20.0.0' @@ -2048,11 +2069,11 @@ packages: react-dom: optional: true - '@docsearch/css@4.3.2': - resolution: {integrity: sha512-K3Yhay9MgkBjJJ0WEL5MxnACModX9xuNt3UlQQkDEDZJZ0+aeWKtOkxHNndMRkMBnHdYvQjxkm6mdlneOtU1IQ==} + '@docsearch/css@4.6.3': + resolution: {integrity: sha512-nlOwcXcsNAptQl4vlL4MA78qNJKO0Qlds5GuBjCoePgkebTXLSf8Qt1oyZ3YBshYupKXG9VRGEsk1zr23d+bzQ==} - '@docsearch/react@4.3.2': - resolution: {integrity: sha512-74SFD6WluwvgsOPqifYOviEEVwDxslxfhakTlra+JviaNcs7KK/rjsPj89kVEoQc9FUxRkAofaJnHIR7pb4TSQ==} + '@docsearch/react@4.6.3': + resolution: {integrity: sha512-Bg2wdDsoQVlNCcEKuEJAU04tvHCqgx8rIu+uIoM4pRtcx3TBKJuXutJik3LTA8LRc9YEyHkrYUrmcC0D7BYf+g==} peerDependencies: '@types/react': '>= 16.8.0 < 20.0.0' react: '>= 16.8.0 < 20.0.0' @@ -2068,12 +2089,12 @@ packages: search-insights: optional: true - '@docusaurus/babel@3.10.0': - resolution: {integrity: sha512-mqCJhCZNZUDg0zgDEaPTM4DnRsisa24HdqTy/qn/MQlbwhTb4WVaZg6ZyX6yIVKqTz8fS1hBMgM+98z+BeJJDg==} + '@docusaurus/babel@3.10.1': + resolution: {integrity: sha512-DZzFO1K3v/GoEt1fx1DiYHF4en+PuhtQf1AkQJa5zu3CoeKSpr5cpQRUlz3jr0m44wyzmSXu9bVpfir+N4+8bg==} engines: {node: '>=20.0'} - '@docusaurus/bundler@3.10.0': - resolution: {integrity: sha512-iONUGZGgp+lAkw/cJZH6irONcF4p8+278IsdRlq8lYhxGjkoNUs0w7F4gVXBYSNChq5KG5/JleTSsdJySShxow==} + '@docusaurus/bundler@3.10.1': + resolution: {integrity: sha512-HIqQPvbqnnQRe4NsBd1774KRarjXqS6wHsWELtyuSs1gCfvixJO2jUGH/OEBtr1Gvzpw+ze5CjGMvSJ8UE1KUw==} engines: {node: '>=20.0'} peerDependencies: '@docusaurus/faster': '*' @@ -2081,8 +2102,8 @@ packages: '@docusaurus/faster': optional: true - '@docusaurus/core@3.10.0': - resolution: {integrity: sha512-mgLdQsO8xppnQZc3LPi+Mf+PkPeyxJeIx11AXAq/14fsaMefInQiMEZUUmrc7J+956G/f7MwE7tn8KZgi3iRcA==} + '@docusaurus/core@3.10.1': + resolution: {integrity: sha512-3pf2fXXw0eVk8WnC3T4LIigRDupcpvngpKo9Vy7mYyBhuddc0klDUuZAIfzMoK6z05pdlk6EFC/vBSX43+1O5w==} engines: {node: '>=20.0'} hasBin: true peerDependencies: @@ -2094,97 +2115,97 @@ packages: '@docusaurus/faster': optional: true - '@docusaurus/cssnano-preset@3.10.0': - resolution: {integrity: sha512-qzSshTO1DB3TYW+dPUal5KHM7XPc5YQfzF3Kdb2NDACJUyGbNcFtw3tGkCJlYwhNCRKbZcmwraKUS1i5dcHdGg==} + '@docusaurus/cssnano-preset@3.10.1': + resolution: {integrity: sha512-eNfHGcTKCSq6xmcavAkX3RRclHaE2xRCMParlDXLdXVP01/a2e/jKXMj/0ULnLFQSNwwuI62L0Ge8J+nZsR7UQ==} engines: {node: '>=20.0'} - '@docusaurus/logger@3.10.0': - resolution: {integrity: sha512-9jrZzFuBH1LDRlZ7cznAhCLmAZ3HSDqgwdrSSZdGHq9SPUOQgXXu8mnxe2ZRB9NS1PCpMTIOVUqDtZPIhMafZg==} + '@docusaurus/logger@3.10.1': + resolution: {integrity: sha512-oPjNFnfJsRCkePVjkGrxWGq4MvJKRQT0r9jOP0eRBTZ7Wr9FAbzdP/Gjs0I2Ss6YRkPoEgygKG112OkE6skvJw==} engines: {node: '>=20.0'} - '@docusaurus/mdx-loader@3.10.0': - resolution: {integrity: sha512-mQQV97080AH4PYNs087l202NMDqRopZA4mg5W76ZZyTFrmWhJ3mHg+8A+drJVENxw5/Q+wHMHLgsx+9z1nEs0A==} + '@docusaurus/mdx-loader@3.10.1': + resolution: {integrity: sha512-GRmeb/wQ+iXRrFwcHBfgQhrJxGElgCsoTWZYDhccjsZVne1p8MK/EpQVIloXttz76TCe78kKD5AEG9n1xc1oxQ==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/module-type-aliases@3.10.0': - resolution: {integrity: sha512-/1O0Zg8w3DFrYX/I6Fbss7OJrtZw1QoyjDhegiFNHVi9A9Y0gQ3jUAytVxF6ywpAWpLyLxch8nN8H/V3XfzdJQ==} + '@docusaurus/module-type-aliases@3.10.1': + resolution: {integrity: sha512-YoOZKUdGlp8xSYhuAkGdSo5Ydkbq4V4eK3sD8v0a2hloxCWdQbNBhkc+Ko9QyjpESc0BYcIGM5iHVAy5hdFV6w==} peerDependencies: react: '*' react-dom: '*' - '@docusaurus/plugin-content-blog@3.10.0': - resolution: {integrity: sha512-RuTz68DhB7CL96QO5UsFbciD7GPYq6QV+YMfF9V0+N4ZgLhJIBgpVAr8GobrKF6NRe5cyWWETU5z5T834piG9g==} + '@docusaurus/plugin-content-blog@3.10.1': + resolution: {integrity: sha512-mmkgE6Q2+K74tnkou7tXlpDLvoCU/qkSa2GSQ3XUiHWvcebCoDQzS670RR3tO8PmaWlIyWWISYWzZLuMfxunRA==} engines: {node: '>=20.0'} peerDependencies: '@docusaurus/plugin-content-docs': '*' react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-content-docs@3.10.0': - resolution: {integrity: sha512-9BjHhf15ct8Z7TThTC0xRndKDVvMKmVsAGAN7W9FpNRzfMdScOGcXtLmcCWtJGvAezjOJIm6CxOYCy3Io5+RnQ==} + '@docusaurus/plugin-content-docs@3.10.1': + resolution: {integrity: sha512-2jRVrtzjf8LClGTHQlwlwuD3wQXRx3WEoF7XUarJ8Ou+0onV+SLtejsyfY9JLpfUh9hPhXM4pbBGkyAY4Bi3HQ==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-content-pages@3.10.0': - resolution: {integrity: sha512-5amX8kEJI+nIGtuLVjYk59Y5utEJ3CHETFOPEE4cooIRLA4xM4iBsA6zFgu4ljcopeYwvBzFEWf5g2I6Yb9SkA==} + '@docusaurus/plugin-content-pages@3.10.1': + resolution: {integrity: sha512-huJpaRPMl42nsFwuCXvV8bVDj2MazuwRJIUylI/RSlmZeJssVoZXeCjVf1y+1Drtpa9SKcdGn8yoJ76IRJijtw==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-css-cascade-layers@3.10.0': - resolution: {integrity: sha512-6q1vtt5FJcg5osgkHeM1euErECNqEZ5Z1j69yiNx2luEBIso+nxCkS9nqj8w+MK5X7rvKEToGhFfOFWncs51pQ==} + '@docusaurus/plugin-css-cascade-layers@3.10.1': + resolution: {integrity: sha512-r//fn+MNHkE1wCof8T29VAQezt1enGCpsFxoziBbvLgBM4JfXN2P3rxrBaavHmvLvm7lYkpJeitcDthwnmWCTw==} engines: {node: '>=20.0'} - '@docusaurus/plugin-debug@3.10.0': - resolution: {integrity: sha512-XcljKN+G+nmmK69uQA1d9BlYU3ZftG3T3zpK8/7Hf/wrOlV7TA4Ampdrdwkg0jElKdKAoSnPhCO0/U3bQGsVQQ==} + '@docusaurus/plugin-debug@3.10.1': + resolution: {integrity: sha512-9KqOpKNfAyqGZykRb9LhIT/vyRF6sm/ykhjj/39JvaJahDS+jZJE0Z1Wfz9q3DUNDTMNN0Q7u/kk4rKKU+IJuA==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-google-analytics@3.10.0': - resolution: {integrity: sha512-hTEoodatpBZnUat5nFExbuTGA1lhWGy7vZGuTew5Q3QDtGKFpSJLYmZJhdTjvCFwv1+qQ67hgAVlKdJOB8TXow==} + '@docusaurus/plugin-google-analytics@3.10.1': + resolution: {integrity: sha512-8o0P1KtmgdYQHH+oInitPpRWI0Of5XednAX4+DMhQNSmGSRNrsEEHg1ebv35m9AgRClfAytCJ5jA9KvcASTyuA==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-google-gtag@3.10.0': - resolution: {integrity: sha512-iB/Zzjv/eelJRbdULZqzWCbgMgJ7ht4ONVjXtN3+BI/muil6S87gQ1OJyPwlXD+ELdKkitC7bWv5eJdYOZLhrQ==} + '@docusaurus/plugin-google-gtag@3.10.1': + resolution: {integrity: sha512-pu3xIUo5o/zCMLfUY9BO5KOwSH0zIsAGyFRPvXHayFSA5XIhCU/SFuB0g0ZNjFn9niZLCaNvoeAuOGFJZq0fdw==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-google-tag-manager@3.10.0': - resolution: {integrity: sha512-FEjZxqKgLHa+Wez/EgKxRwvArNCWIScfyEQD95rot7jkxp6nonjI5XIbGfO/iYhM5Qinwe8aIEQHP2KZtpqVuA==} + '@docusaurus/plugin-google-tag-manager@3.10.1': + resolution: {integrity: sha512-f6fyGHiCm7kJHBtAisGQS5oNBnpnMTYQZxDXeVrnw/3zWU+LMA22pr6UHGYkBKDbN+qPC5QHG3NuOfzQLq3+Lw==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-sitemap@3.10.0': - resolution: {integrity: sha512-DVTSLjB97hIjmayGnGcBfognCeI7ZuUKgEnU7Oz81JYqXtVg94mVTthDjq3QHTylYNeCUbkaW8VF0FDLcc8pPw==} + '@docusaurus/plugin-sitemap@3.10.1': + resolution: {integrity: sha512-C26MbmmqgdjkDq1htaZ3aD7LzEDKFWXfpyQpt0EOUThuq5nV77zDaedV20yHcVo9p+3ey9aZ4pbHA0D3QcZTzg==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-svgr@3.10.0': - resolution: {integrity: sha512-lNljBESaETZqVBMPqkrGchr+UPT1eZzEPLmJhz8I76BxbjqgsUnRvrq6lQJ9sYjgmgX52KB7kkgczqd2yzoswQ==} + '@docusaurus/plugin-svgr@3.10.1': + resolution: {integrity: sha512-6SFxsmjWFkVLDmBUvFK6i72QjUwqyQFe4Ovz+SUJophJjOyVG3ZZG5IQpBC/kX/Gfv1yWeU9nWauH6F6Q7QX/Q==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/preset-classic@3.10.0': - resolution: {integrity: sha512-kw/Ye02Hc6xP1OdTswy8yxQEHg0fdPpyWAQRxr5b2x3h7LlG2Zgbb5BDFROnXDDMpUxB7YejlocJIE5HIEfpNA==} + '@docusaurus/preset-classic@3.10.1': + resolution: {integrity: sha512-YO/FL8v1zmbxoTso6mjMz/RDjhaTJxb1UpFFTDdY5847LLDCeyYiYlrhyTbgN1RIN3xnkLKZ9Lj1x8hUzI4JOg==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 @@ -2195,23 +2216,23 @@ packages: peerDependencies: react: '*' - '@docusaurus/theme-classic@3.10.0': - resolution: {integrity: sha512-9msCAsRdN+UG+RwPwCFb0uKy4tGoPh5YfBozXeGUtIeAgsMdn6f3G/oY861luZ3t8S2ET8S9Y/1GnpJAGWytww==} + '@docusaurus/theme-classic@3.10.1': + resolution: {integrity: sha512-VU1RK0qb2pab0si4r7HFK37cYco8VzqLj3u1PspVipSr/z/GPVKHO4/HXbnePqHoWDk8urjyGSeatH0NIMBM1A==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/theme-common@3.10.0': - resolution: {integrity: sha512-Dkp1YXKn16ByCJAdIjbDIOpVb4Z66MsVD694/ilX1vAAHaVEMrVsf/NPd9VgreyFx08rJ9GqV1MtzsbTcU73Kg==} + '@docusaurus/theme-common@3.10.1': + resolution: {integrity: sha512-0YtmIeoNo1fIw65LO8+/1dPgmDV86UmhMkow37gzjytuiCSQm9xob6PJy0L4kuQEMTLfUOGvkXvZr7GPrHquMA==} engines: {node: '>=20.0'} peerDependencies: '@docusaurus/plugin-content-docs': '*' react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/theme-mermaid@3.10.0': - resolution: {integrity: sha512-Y2xrlwhIJ80oOZIO3PXL6A7J869splfcMI87E3NKpYsy3zJxOyV+BP1QMtGi59ajKgU868HPuyyn6J+6BZGOBg==} + '@docusaurus/theme-mermaid@3.10.1': + resolution: {integrity: sha512-2gxpmln8Pc4EN1oWzshQEx2HTs67jk14v7MmgqGs8ZU7Nm8oihg+fTouof2u4vN8DtB3Fln4cDJu4UprSX1S3Q==} engines: {node: '>=20.0'} peerDependencies: '@mermaid-js/layout-elk': ^0.1.9 @@ -2221,36 +2242,36 @@ packages: '@mermaid-js/layout-elk': optional: true - '@docusaurus/theme-search-algolia@3.10.0': - resolution: {integrity: sha512-f5FPKI08e3JRG63vR/o4qeuUVHUHzFzM0nnF+AkB67soAZgNsKJRf2qmUZvlQkGwlV+QFkKe4D0ANMh1jToU3g==} + '@docusaurus/theme-search-algolia@3.10.1': + resolution: {integrity: sha512-OTaARARVZj2GvkJQjB+1jOIxntRaXea+G+fMsNqrZBAU1O1vJKDW22R7kECOHW27oJCLFN9HKaZeRrfAUyviug==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/theme-translations@3.10.0': - resolution: {integrity: sha512-L9IbFLwTc5+XdgH45iQYufLn0SVZd6BUNelDbKIFlH+E4hhjuj/XHWAFMX/w2K59rfy8wak9McOaei7BSUfRPA==} + '@docusaurus/theme-translations@3.10.1': + resolution: {integrity: sha512-cLMyaKivjBVWKMJuWqyFVVgtqe8DPJNPkog0bn8W1MDVAKcPdxRFycBfC1We1RaNp7Rdk513bmtW78RR6OBxBw==} engines: {node: '>=20.0'} - '@docusaurus/tsconfig@3.10.0': - resolution: {integrity: sha512-TXdC3WXuPrdQAexLvjUJfnYf3YKEgEqAs5nK0Q88pRBCW7t7oN4ILvWYb3A5Z1wlSXyXGWW/mCUmLEhdWsjnDQ==} + '@docusaurus/tsconfig@3.10.1': + resolution: {integrity: sha512-rYvB7yqkdqWIpAbDzQljGfM4cDBkLTbhmagZBEcsyj6oPUsz47lmW2pYdN1j+7sGFgltbAmQH62xfbrij4Eh6Q==} - '@docusaurus/types@3.10.0': - resolution: {integrity: sha512-F0dOt3FOoO20rRaFK7whGFQZ3ggyrWEdQc/c8/UiRuzhtg4y1w9FspXH5zpCT07uMnJKBPGh+qNazbNlCQqvSw==} + '@docusaurus/types@3.10.1': + resolution: {integrity: sha512-XYMK8k1szDCFMw2V+Xyen0g7Kee1sP3dtFnl7vkGkZOkeAJ/oPDQPL8iz4HBKOo/cwU8QeV6onVjMqtP+tFzsw==} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/utils-common@3.10.0': - resolution: {integrity: sha512-JyL7sb9QVDgYvudIS81Dv0lsWm7le0vGZSDwsztxWam1SPBqrnkvBy9UYL/amh6pbybkyYTd3CMTkO24oMlCSw==} + '@docusaurus/utils-common@3.10.1': + resolution: {integrity: sha512-5mFSgEADtnFxFH7RLw02QA5MpU5JVUCj0MPeIvi/aF4Fi45tQRIuTwXoXDqJ+1VfQJuYJGz3SI63wmGz4HvXzA==} engines: {node: '>=20.0'} - '@docusaurus/utils-validation@3.10.0': - resolution: {integrity: sha512-c+6n2+ZPOJtWWc8Bb/EYdpSDfjYEScdCu9fB/SNjOmSCf1IdVnGf2T53o0tsz0gDRtCL90tifTL0JE/oMuP1Mw==} + '@docusaurus/utils-validation@3.10.1': + resolution: {integrity: sha512-cRv1X69jwaWv47waglllgZVWzeBFLhl53XT/XED/83BerVBTC5FTP8WTcVl8Z6sZOegDSwitu/wpCSPCDOT6lg==} engines: {node: '>=20.0'} - '@docusaurus/utils@3.10.0': - resolution: {integrity: sha512-T3B0WTigsIthe0D4LQa2k+7bJY+c3WS+Wq2JhcznOSpn1lSN64yNtHQXboCj3QnUs1EuAZszQG1SHKu5w5ZrlA==} + '@docusaurus/utils@3.10.1': + resolution: {integrity: sha512-3ojeJry9xBYdJO6qoyyzqeJFSJBVx2mXhyDzSdjwL2+URFQMf+h25gG38iswGImicK0ELjTd1EL2xzk8hf3QPw==} engines: {node: '>=20.0'} '@emnapi/core@1.10.0': @@ -2274,6 +2295,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.28.0': resolution: {integrity: sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==} engines: {node: '>=18'} @@ -2292,6 +2319,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.28.0': resolution: {integrity: sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==} engines: {node: '>=18'} @@ -2310,6 +2343,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.28.0': resolution: {integrity: sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==} engines: {node: '>=18'} @@ -2328,6 +2367,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.28.0': resolution: {integrity: sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==} engines: {node: '>=18'} @@ -2346,6 +2391,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.28.0': resolution: {integrity: sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==} engines: {node: '>=18'} @@ -2364,6 +2415,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.28.0': resolution: {integrity: sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==} engines: {node: '>=18'} @@ -2382,6 +2439,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.28.0': resolution: {integrity: sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==} engines: {node: '>=18'} @@ -2400,6 +2463,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.28.0': resolution: {integrity: sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==} engines: {node: '>=18'} @@ -2418,6 +2487,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.28.0': resolution: {integrity: sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==} engines: {node: '>=18'} @@ -2436,6 +2511,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.28.0': resolution: {integrity: sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==} engines: {node: '>=18'} @@ -2454,6 +2535,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.28.0': resolution: {integrity: sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==} engines: {node: '>=18'} @@ -2472,6 +2559,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.28.0': resolution: {integrity: sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==} engines: {node: '>=18'} @@ -2490,6 +2583,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.28.0': resolution: {integrity: sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==} engines: {node: '>=18'} @@ -2508,6 +2607,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.28.0': resolution: {integrity: sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==} engines: {node: '>=18'} @@ -2526,6 +2631,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.28.0': resolution: {integrity: sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==} engines: {node: '>=18'} @@ -2544,6 +2655,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.28.0': resolution: {integrity: sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==} engines: {node: '>=18'} @@ -2562,6 +2679,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.28.0': resolution: {integrity: sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==} engines: {node: '>=18'} @@ -2574,6 +2697,12 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-arm64@0.28.0': resolution: {integrity: sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==} engines: {node: '>=18'} @@ -2592,6 +2721,12 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.28.0': resolution: {integrity: sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==} engines: {node: '>=18'} @@ -2604,6 +2739,12 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-arm64@0.28.0': resolution: {integrity: sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==} engines: {node: '>=18'} @@ -2622,6 +2763,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.28.0': resolution: {integrity: sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==} engines: {node: '>=18'} @@ -2634,6 +2781,12 @@ packages: cpu: [arm64] os: [openharmony] + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/openharmony-arm64@0.28.0': resolution: {integrity: sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==} engines: {node: '>=18'} @@ -2652,6 +2805,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.28.0': resolution: {integrity: sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==} engines: {node: '>=18'} @@ -2670,6 +2829,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.28.0': resolution: {integrity: sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==} engines: {node: '>=18'} @@ -2688,6 +2853,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.28.0': resolution: {integrity: sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==} engines: {node: '>=18'} @@ -2706,6 +2877,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.28.0': resolution: {integrity: sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==} engines: {node: '>=18'} @@ -2726,16 +2903,16 @@ packages: resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/config-helpers@0.5.5': - resolution: {integrity: sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==} + '@eslint/config-helpers@0.6.0': + resolution: {integrity: sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/core@1.2.1': resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/css-tree@4.0.2': - resolution: {integrity: sha512-eqSkC3mka2tiqOuPZKqvxNJoRzpxMss3Np3Yqi4sW7nTTRCpTKB2hzrY4JRsi0ZP3QbVfp23sgEm7VCoOjesmw==} + '@eslint/css-tree@4.0.3': + resolution: {integrity: sha512-lMgQJ5zg4YwsGPWtKS7Rc5Z4xqc2B9iOTtmDvhjMRa8GJ8/9zoKRtz26D7gCtWquy0J7oyeOJBwRP5M8slM/4w==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/js@10.0.1': @@ -2785,20 +2962,20 @@ packages: '@formatjs/fast-memoize@2.2.7': resolution: {integrity: sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==} - '@formatjs/fast-memoize@3.1.2': - resolution: {integrity: sha512-vPnriihkfK0lzoQGaXq+qXH23VsYyansRTkTgo2aTG0k1NjLFyZimFVdfj4C9JkSE5dm7CEngcQ5TTc1yAyBfQ==} + '@formatjs/fast-memoize@3.1.5': + resolution: {integrity: sha512-KLi3fan6WnCHmigd9pmEEN8Hid0v4wiFBW576M/d07KMWYecf1CvyMI3n34vCmHT4AoVqG2n702kiHbXjzZX2A==} '@formatjs/icu-messageformat-parser@2.11.4': resolution: {integrity: sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==} - '@formatjs/icu-messageformat-parser@3.5.4': - resolution: {integrity: sha512-JVY39ROgLt+pIYngo6piyj4OVfZmXs/2FkC4wLS+ql1Eig/sGJKB7YwDO/5bkJFkfwaFAeIpgEiJc8hiYxNalw==} + '@formatjs/icu-messageformat-parser@3.5.9': + resolution: {integrity: sha512-PZm6O9JI/gUPtQV9r2eaMuLb4yWqV2vz+ot03ORHWTKO343LSpZi0TqeXLB2ZZGDXLCw2SbfgsQ0GxoxXMl79g==} '@formatjs/icu-skeleton-parser@1.8.16': resolution: {integrity: sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==} - '@formatjs/icu-skeleton-parser@2.1.4': - resolution: {integrity: sha512-8bSFZbrlvGX11ywMZxtgkPBt5Q8/etyts7j7j+GWpOVK1g43zwMIH3LZxk43HAtEP7L/jtZ+OZaMiFTOiBj9CA==} + '@formatjs/icu-skeleton-parser@2.1.9': + resolution: {integrity: sha512-rsxswgHMfU1zUgB2byc08fesf83wLGjFnzLCEtuf00mx2doiqc6pYrf67raI37XqdRcGUviQepk2UKGqpng74Q==} '@formatjs/intl-localematcher@0.6.2': resolution: {integrity: sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==} @@ -2830,8 +3007,8 @@ packages: engines: {node: '>=6'} hasBin: true - '@grpc/proto-loader@0.8.0': - resolution: {integrity: sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==} + '@grpc/proto-loader@0.8.1': + resolution: {integrity: sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==} engines: {node: '>=6'} hasBin: true @@ -2864,11 +3041,11 @@ packages: '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} - '@iconify/utils@3.1.0': - resolution: {integrity: sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==} + '@iconify/utils@3.1.3': + resolution: {integrity: sha512-LPKOXPn/zV+zis1oOfGWogaXVpqUybF3ZS6SCZIsz8vg0ivVp9+fVqyYB7xq0aiST/VhUQYGO1qo6uoYSiEJqw==} - '@img/colour@1.0.0': - resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} engines: {node: '>=18'} '@img/sharp-darwin-arm64@0.34.5': @@ -3364,8 +3541,8 @@ packages: resolution: {integrity: sha512-x/iUDjcS90W69PryLDIMgFyV21YLTnG9zOpPXS7Bkt2b8AsY3zZsIpOLBkYr9fBcF3HbkKaER5hOBZLfpLgYNw==} engines: {node: '>= 14.0.0'} - '@koa/router@15.4.0': - resolution: {integrity: sha512-vKYlXtoCfcAN8z4dHiveYX55rTYOgHEYJNumK1WM9ZAwaArhreGVkyC1LTMGfUQUJyIO/SbwRFBOHeOCY8/MaQ==} + '@koa/router@15.5.0': + resolution: {integrity: sha512-KSC0oG/5t6ITu5wqX4lJseA/dngoj14hEaohrLZEXtlUT2RRyJvwaJ0KV+5uQoaWrY3A8ClHOrBEU4g8dujn8Q==} engines: {node: '>= 20'} peerDependencies: koa: ^2.0.0 || ^3.0.0 @@ -3409,8 +3586,8 @@ packages: '@mapbox/point-geometry@1.1.0': resolution: {integrity: sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==} - '@mapbox/tiny-sdf@2.1.0': - resolution: {integrity: sha512-uFJhNh36BR4OCuWIEiWaEix9CA2WzT6CAIcqVjWYpnx8+QDtS+oC4QehRrx5cX4mgWs37MmKnwUejeHxVymzNg==} + '@mapbox/tiny-sdf@2.2.0': + resolution: {integrity: sha512-LVL4wgI9YAum5V+LNVQO6QgFBPw7/MIIY4XJPNsPDMrjEwcE+JfKk1LuIl8GnF197ejVdC9QdPaxrx5gfgdGXg==} '@mapbox/unitbezier@0.0.1': resolution: {integrity: sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==} @@ -3428,8 +3605,8 @@ packages: '@maplibre/geojson-vt@6.1.0': resolution: {integrity: sha512-2eIY4gZxeKIVOZVNkAMb+5NgXhgsMQpOveTQAvnp53LYqHGJZDidk7Ew0Tged9PThidpbS+NFTh0g4zivhPDzQ==} - '@maplibre/maplibre-gl-style-spec@24.8.1': - resolution: {integrity: sha512-zxa92qF96ZNojLxeAjnaRpjVCy+swoUNJvDhtpC90k7u5F0TMr4GmvNqMKvYrMoPB8d7gRSXbMG1hBbmgESIsw==} + '@maplibre/maplibre-gl-style-spec@24.8.5': + resolution: {integrity: sha512-EzEJmMt6thioRH7GI9LWS7ahXTcAhAPGWCe6oTP2Ps4YnsXOOAfeqx854lZaiDnwURfHmcCKV1mr6oo0i23x6w==} hasBin: true '@maplibre/mlt@1.1.9': @@ -3462,8 +3639,8 @@ packages: '@types/react': '>=16' react: '>=16' - '@mermaid-js/parser@0.6.3': - resolution: {integrity: sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==} + '@mermaid-js/parser@1.1.1': + resolution: {integrity: sha512-VuHdsYMK1bT6X2JbcAaWAhugTRvRBRyuZgd+c22swUeI9g/ntaxF7CY7dYarhZovofCbUNO0G7JesfmNtjYOCw==} '@microsoft/tsdoc@0.16.0': resolution: {integrity: sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==} @@ -3533,8 +3710,8 @@ packages: '@swc/core': optional: true - '@nestjs/common@11.1.19': - resolution: {integrity: sha512-qeiTt2tv+e5QyDKqG8HlVZb2wx64FEaSGFJouqTSRs+kG44iTfl3xlz1XqVped+rihx4hmjWgL5gkhtdK3E6+Q==} + '@nestjs/common@11.1.21': + resolution: {integrity: sha512-YV1HYDGsm2rnR0vrLKidtrG6jYX5yqiIjeur1j8++dKGqhhsJ6cjMs0RfQRSTUH7IjgDemA59/znQ8nRrE0D9g==} peerDependencies: class-transformer: '>=0.4.1' class-validator: '>=0.13.2' @@ -3546,8 +3723,8 @@ packages: class-validator: optional: true - '@nestjs/core@11.1.19': - resolution: {integrity: sha512-6nJkWa2efrYi+XlU686J9y5L7OvxpLVjT0T/sxRKE7Jvpffiihelup4WSvLvRhdHDjj/5SuoWEwqReXAaaeHmw==} + '@nestjs/core@11.1.21': + resolution: {integrity: sha512-fqo0BHgny3MOuAL8GSfG3ZUKFVVBaBQD/0iyibnwTONT5vPexjQxJzu+945iloVvBDmrnAaRWxC1gqCDEs/AXQ==} engines: {node: '>= 20'} peerDependencies: '@nestjs/common': ^11.0.0 @@ -3577,14 +3754,14 @@ packages: class-validator: optional: true - '@nestjs/platform-express@11.1.19': - resolution: {integrity: sha512-Vpdv8jyCQdThfoTx+UTn+DRYr6H6X02YUqcpZ3qP6G3ZUwtVp7eS+hoQPGd4UuCnlnFG8Wqr2J9bGEzQdi1rIg==} + '@nestjs/platform-express@11.1.21': + resolution: {integrity: sha512-lA3ViycOnz4Df3EstIKpuAVFhqxQixTnjAVk0M+LRyNBlGM6VSCaNJaAIrb9Pcry39T4hTHpNVbRqGLSvhL8gA==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/core': ^11.0.0 - '@nestjs/platform-socket.io@11.1.19': - resolution: {integrity: sha512-gu1nPIEaP5Qjjg/Cl8wXyvwGpdZGzgbtK4KcH65YRAA+GTKUkIHb4BNpLJ27Ymq/wqLJKNEbCjajfzD0BEjMGA==} + '@nestjs/platform-socket.io@11.1.21': + resolution: {integrity: sha512-Tq5JgaVS+auD3DXuRBy8UMU3mf69HJO8Ep+BuRS9GYMXGd/5sdMHqIQvXlXkGih9tQXdeeG9WoqURe/+IjPKng==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/websockets': ^11.0.0 @@ -3605,8 +3782,8 @@ packages: prettier: optional: true - '@nestjs/swagger@11.4.2': - resolution: {integrity: sha512-aBihEogDMj/bLEcaqhkvyX/ZVWUw/bmnhKzR0zwUoyGJikvZyaq7rOPYl/H7Lxkkr3c90SJxyuv1AX2UT1WKlw==} + '@nestjs/swagger@11.4.3': + resolution: {integrity: sha512-LR4BuOj+iBFzhGRnNP0OHjmrPXliDEjrmniXtLsfLDIELjkuUXYCTGjZMqgDdOY+QSabeF59LndaDzOOe+vMmw==} peerDependencies: '@fastify/static': ^8.0.0 || ^9.0.0 '@nestjs/common': ^11.0.1 @@ -3622,8 +3799,8 @@ packages: class-validator: optional: true - '@nestjs/testing@11.1.19': - resolution: {integrity: sha512-/UFNWXvPEdu4v4DlC5oWLbGKmD27LehLK06b8oLzs6D6lf4vAQTdST8LRAXBadyMUQnVEQWMuBo3CtAVtlfXtQ==} + '@nestjs/testing@11.1.21': + resolution: {integrity: sha512-RhzaUFxr6/bpXWjKIzr7p2eHKMFMLwPgsxJNFcCf2CkkT3UEjW+KRGb7E2JY+fh+ck3zAdvQJrzATDnSsVlFZw==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/core': ^11.0.0 @@ -3635,8 +3812,8 @@ packages: '@nestjs/platform-express': optional: true - '@nestjs/websockets@11.1.19': - resolution: {integrity: sha512-2qo8jtIwwwgkqAI1BtnJ02EaFLrRkKA39eYXS8IhZCHilhBHCWdjnJ5cLcFq4oF+s+KZ7LcLGD/3stxJy8ijzg==} + '@nestjs/websockets@11.1.21': + resolution: {integrity: sha512-2L+jFf6Nbjv7WacngvSoYGYTalqNuXlOkFl9Q5e92XiPK4gR6c4Zw6zFcb7btTY5zf7pITvOoAJIGwf3+gciRA==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/core': ^11.0.0 @@ -3647,6 +3824,10 @@ packages: '@nestjs/platform-socket.io': optional: true + '@noble/hashes@1.4.0': + resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} + engines: {node: '>= 16'} + '@noble/hashes@1.8.0': resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} engines: {node: ^14.21.3 || >=16} @@ -3667,14 +3848,6 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@npmcli/agent@4.0.0': - resolution: {integrity: sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==} - engines: {node: ^20.17.0 || >=22.9.0} - - '@npmcli/fs@5.0.0': - resolution: {integrity: sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==} - engines: {node: ^20.17.0 || >=22.9.0} - '@nuxt/opencollective@0.4.1': resolution: {integrity: sha512-GXD3wy50qYbxCJ652bDrDzgMr3NFEkIS374+IgFQKkCvk9yiYcLvX2XDYr7UyQxf4wK0e+yqDYRubZ0DtOxnmQ==} engines: {node: ^14.18.0 || >=16.10.0, npm: '>=5.10.0'} @@ -3683,24 +3856,16 @@ packages: '@oazapfts/runtime@1.2.0': resolution: {integrity: sha512-fi7dp7dNayyh/vzqhf0ZdoPfC7tJvYfjaE8MBL1yR+iIsH7cFoqHt+DV70VU49OMCqLc7wQa+yVJcSmIRnV4wA==} - '@opentelemetry/api-logs@0.215.0': - resolution: {integrity: sha512-xrFlqhdhUyO8wSRn6DjE0145/HPWSJ5Nm0C7vWua6TdL/FSEAZvEyvdsa9CRXuxo9ebb7j/NEPhEcO62IJ0qUA==} - engines: {node: '>=8.0.0'} - - '@opentelemetry/api-logs@0.217.0': - resolution: {integrity: sha512-Cdq0jW2lknrNfrAm92MyEAvpe2cRsKjdnQLHUL6xRA4IVUnsWx6P65E7NcUO0Y+L4w1Aee5iV8FvjSwd+lrs9A==} - engines: {node: '>=8.0.0'} - - '@opentelemetry/api@1.9.0': - resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + '@opentelemetry/api-logs@0.218.0': + resolution: {integrity: sha512-fmEWp5kXlGEc3i/lR698Hz41DfGyN4Tbe4g7L1AxSc7fF8Xeh/FQ9Quqpa9dVA413Q1Ad43QOLzU4JoXgbFPWw==} engines: {node: '>=8.0.0'} '@opentelemetry/api@1.9.1': resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} engines: {node: '>=8.0.0'} - '@opentelemetry/configuration@0.217.0': - resolution: {integrity: sha512-xCtrYOhBqdy6ZOMfe0Oa73ZKF+2LMhoOv4L5vmwAHVvOXUg+V3fvKuEIr9ZyD0Ow+vxllEjWO6PV1wd0DOtyvw==} + '@opentelemetry/configuration@0.218.0': + resolution: {integrity: sha512-W8wIz7H2R1pufR5jfjb3gU2XkMpm2x/7b1RJcsuzvd70Il/rWWE+g5/Od7hQKrxRTSrTrOWlru101PWXz5I1EQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.9.0 @@ -3711,74 +3876,68 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/core@2.7.0': - resolution: {integrity: sha512-DT12SXVwV2eoJrGf4nnsvZojxxeQo+LlNAsoYGRRObPWTeN6APiqZ2+nqDCQDvQX40eLi1AePONS0onoASp3yQ==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/core@2.7.1': resolution: {integrity: sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/exporter-logs-otlp-grpc@0.217.0': - resolution: {integrity: sha512-vC5S0Dc+noxD86CVtNu1+awCHPA5Kewi1Sg23ps+9lh4YifwsKXh3pe4XTNEKtUJiAcjpJ5dqStGakLbrSE+YQ==} + '@opentelemetry/exporter-logs-otlp-grpc@0.218.0': + resolution: {integrity: sha512-hoxrNH1l/Xy6F9WTJ5IK+6j1r9nQFlPOmrnTlhYHTySdunfXLmUCPv3bQtKYntxag9h3wLYBZQ2HI6FOx+BT2g==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-logs-otlp-http@0.217.0': - resolution: {integrity: sha512-KfLAdt1uilVE+3FxbgVnp2ZrzqbIawzcesnRoi+Kh9ckB5Ld5D8btUgoBvwTbdmuNx1j6b132Wsh72azq+pPNQ==} + '@opentelemetry/exporter-logs-otlp-http@0.218.0': + resolution: {integrity: sha512-Qx+4rpVHzgg89dawcWRHyt+XRXeLnhFz/qBtvggmjkcgPUdr+NAB0/u/eIPA8yAeJV0J80Vz43JZCh/XFvZFGw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-logs-otlp-proto@0.217.0': - resolution: {integrity: sha512-Se0GG/ZO24mQTlQj7zprR4pNI0nKe4lPDPBsuJmi6508b9TlZEuUd3EfyuHk6oJxzL7fGyDFYAbxNigQvRP2ZQ==} + '@opentelemetry/exporter-logs-otlp-proto@0.218.0': + resolution: {integrity: sha512-1/noQNsp9gXD75HPzgjBrcF1+XTtry7pFAUfxVEJgg7mPv2AawKQuYkhMmJ8qjxz4Ubc3Y8bwvfxevXsKTq4cg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-metrics-otlp-grpc@0.217.0': - resolution: {integrity: sha512-0GpJKnCoVaVA1rKBMVPHziznfOQlXgH72S9ktjBAF1AnAVPzX7vVEBGrhwiSxxHDAiefXk+J8znApsMb/K6Z3w==} + '@opentelemetry/exporter-metrics-otlp-grpc@0.218.0': + resolution: {integrity: sha512-YapQ9vNMX0NSZF6LK5pWAFfjpJleV2O9uYWfYGeb/5F1Kb9rPGK8tZDMJFa/sOksgdFuflDvYuA0B4qjDB4fjQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-metrics-otlp-http@0.217.0': - resolution: {integrity: sha512-1zkMzzhiNJdVmLxuwkltqWGw4fOOam47bqRxmuQNjyKJe/9NmY5cIrZ4kiQV7sVGxoOgT0ZvGUfLcjvtpC/b9Q==} + '@opentelemetry/exporter-metrics-otlp-http@0.218.0': + resolution: {integrity: sha512-bV7d2OuMpZu2+gAaxUAhzfZ0h3WVZk8ETQUEE3DNSntbTaMpuITjtm8I0rNyHFdm7Ax57K6ty7SgFXlBmOLIvQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-metrics-otlp-proto@0.217.0': - resolution: {integrity: sha512-nfxt/KxVGFkjkO/M+58y1ugHu/dwPtxG4eYq0KApcQ7xk5CHzhdn+IuLZfDSvNDrJ3Uy5q++Fj/wbK7i8yryfQ==} + '@opentelemetry/exporter-metrics-otlp-proto@0.218.0': + resolution: {integrity: sha512-ubLddKjWULhla9YZRCj/rTBeppjJYE4e9w0icx5mTu3eFhWjQzbV75NYjXuIlEG+NJsBl6d+sTFw5Qu+oej4oQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-prometheus@0.217.0': - resolution: {integrity: sha512-U9MCXxJu0sBCh5aEkylYRR4xVIL8D1CW6dGwvYXbfFr0qveSorfD0XJchCAWoW6QfAAIcY/yxjf4Dj8OgkHBPw==} + '@opentelemetry/exporter-prometheus@0.218.0': + resolution: {integrity: sha512-RT5oEyu1kddZJ1vt7/BUo5wV+P7hpNAESsR3dUd3+8deHuX7gWNoCOZn+SfDT+hJHlIJ5h/AxiCLXIrutswDJg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-trace-otlp-grpc@0.217.0': - resolution: {integrity: sha512-fPZs2fw7veLH3pEKu8vSepUa2fQpAE2P7al6qU10aH9GrEJJ8YaPgsd5xON7by5rbcEVS71FOU2aWyK6nzB7VQ==} + '@opentelemetry/exporter-trace-otlp-grpc@0.218.0': + resolution: {integrity: sha512-3fXxVQEj9TNAFaCi79JeFKfeLd0sDtInaR3gaZDVlzNSPHtz8PZuCV34JKWjD4XXzT20IdMe8IpX6mRVNDA4Tw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-trace-otlp-http@0.217.0': - resolution: {integrity: sha512-38YQoqtYjglz2GV94LGUN/djLvxtvGIQO68o6qAFPVshjmwSdX1F2i0c7vn3lEl1L5B/YqjB/bgKXaVx7KO+RQ==} + '@opentelemetry/exporter-trace-otlp-http@0.218.0': + resolution: {integrity: sha512-8dqezsmPhtKitIK/eTipZhYl9EX2/gNQ5zUMhaz3uxEURwfkNf8IPvo6yNfrzbxdtpAOybS/+h7wmIWYqFSpiw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-trace-otlp-proto@0.217.0': - resolution: {integrity: sha512-nPV8gKHUiSuTZpQcnZU3/pBlK7crSyEGpZuh5MtWySB0vv6NNG0QvvfKitQt+Fc2Mc6qfyU54KlZcurwoTbrVg==} + '@opentelemetry/exporter-trace-otlp-proto@0.218.0': + resolution: {integrity: sha512-r1Msf8SNLRmwh9J6XQ5uh82D7CdDWMNHnPB7LAVHjzut0TkSeKc5KcIvr4SvHvfk/xwN5gxC+VLKQ1k0o8PSPw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 @@ -3795,56 +3954,50 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-http@0.215.0': - resolution: {integrity: sha512-ip9iNoRRVxDyP8LVfdqqI6OwbOwzxTl4SaP1WDKJq0sDsgpOr7rIOFj7gV8yKl4F5PdDOUYy8VqdgIOWZRlGBw==} + '@opentelemetry/instrumentation-http@0.218.0': + resolution: {integrity: sha512-x9djaqdzpT8WAboep1H9nCAQ1E+MMsm08TNfA02TqM3bNNddZeiim+E3KMWVQFaX6JpUy7V0nm/wfN/K2Em+Zw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-ioredis@0.63.0': - resolution: {integrity: sha512-x+h/uq7mstqr7TwU1q0MdmMkyU1SDZcmd/ErXbdNfScmXMcYfo8sCRzMsL9UwukSdaU3ccYYpYweGXghv9xN0Q==} + '@opentelemetry/instrumentation-ioredis@0.66.0': + resolution: {integrity: sha512-UfTAcaBKCzLUZ9opvfOLV4bH46XiNFqUsKykfPCIefDIxJ1iUYtMOucNaiZ+/kjQdPy5i6Ef5tk2IAjxol4X1w==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-nestjs-core@0.61.0': - resolution: {integrity: sha512-e/zpwFbEyQFK8uINyFqbeQsA6PW5+hKI+eJj8L98lz1FnQSbRsNMz3Z8c0KYWcDqbg857DpB97s9P3lXdtwccg==} + '@opentelemetry/instrumentation-nestjs-core@0.64.0': + resolution: {integrity: sha512-PW1ArxryMwF8/IXq1nzlQs7tmr/fWd1tf71AHevZT3Fm0hW7jRX9JEfYgIAcKDvmbqcJEr5K1224NEimrRPbuQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-pg@0.67.0': - resolution: {integrity: sha512-1b1o/9nelDwoE3+EucZ9eHZsdUgji799C94lX1ZPy6O0EVjdTj3HczLL6z3GqPGZHmV4OpmJjGz8kuLtuPjCGA==} + '@opentelemetry/instrumentation-pg@0.70.0': + resolution: {integrity: sha512-g8WXwwOUXfjiEmATwjB/33QKE2AkIpNe4KIuJJh4djtXgCL0Wne+AzAfjuDIAspGvO1txQp8ibKsLd3SBmcvJA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation@0.215.0': - resolution: {integrity: sha512-SyJONuqypQ2xWdYMy99vF7JhZ2kDTGx4oRmM/jZV+kRtZ96JTnJmEINbIJgHz7Gnhtw0bimHwbPy/pguA5wpPQ==} + '@opentelemetry/instrumentation@0.218.0': + resolution: {integrity: sha512-mIZil8Es+sYDK5m+DQiwAwF57F14TF2YlEqvIjZ/RQWcxDBwRGsKfdK2Tv65OU9meQKCMzSIFS9mxAcnAb6Bkg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation@0.217.0': - resolution: {integrity: sha512-24ucQMjz7Y34Kw3trbxL2ZrssbtgWnR+Clpaa+YdeWuuyH3Cvk23Q03PcQvqiZrDvt8AmQmjgg9v6Y9PHoxG7w==} + '@opentelemetry/otlp-exporter-base@0.218.0': + resolution: {integrity: sha512-ZwqpkNL5W7RyGJPDZ9g06DvKp8KFTWPJPN12anpMQYSKpTSU0z3EIZuPq9vPGpS8siFyOqDYDAuCwlNO9FqgbA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/otlp-exporter-base@0.217.0': - resolution: {integrity: sha512-eYfqnB3UhKu/5frhd1R6+FprKygbhkomuaceMXDyzxbfXB9tKgZOVmjaJ02CkLA6Tdzumxl+e2H+vo2a8jiMPQ==} + '@opentelemetry/otlp-grpc-exporter-base@0.218.0': + resolution: {integrity: sha512-H/lCGJ536N98VpYJOaWTQOkv4Dx6TnmStK6Rqfu1W7KkFbPAx04hjdYEMZF/YbnHzPUSIK4kM6OE2GKGBTpV9A==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/otlp-grpc-exporter-base@0.217.0': - resolution: {integrity: sha512-7RTAdZuOsCDnsyqTCG4+bDzrfnsWdzkRs7z0AVi/V3tEQx0oKeyc+OuRWYxnRsmaJXgxcmB8vb/lfxn58Dj6Ag==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - - '@opentelemetry/otlp-transformer@0.217.0': - resolution: {integrity: sha512-MKK8UHKFUOGAvbZRWh90MhwHG+Fxm6OROBdjKPCF+HQobjuJ/Kuf8Chs8CR45X1aqotxrMj7OxTdsXe8sXuGVA==} + '@opentelemetry/otlp-transformer@0.218.0': + resolution: {integrity: sha512-CFaKH87WAzjuJ4awowTTLzUvMfaRfiOFG5+qm5S5ncyalRtN4ecQ+YmuANJSCrVPuvZFEkUgKhBPBndxi3rHsQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 @@ -3871,8 +4024,8 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/sdk-logs@0.217.0': - resolution: {integrity: sha512-BB+PcHItcZDL63dPMW+mJvwN9rk37wuIDjRxbVlg6pPDvDR/7GL7UJHbGsllgoggOoTimsKgENaWPoGch/oE1A==} + '@opentelemetry/sdk-logs@0.218.0': + resolution: {integrity: sha512-QvnNdugatFTVCJXH0Mcu7GOOJSylA9j127kIezOE4YwTI4YbowRons2K4WZTv5FMS8T4q9P0NdaRHdkSmeAIag==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.4.0 <1.10.0' @@ -3883,8 +4036,8 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.9.0 <1.10.0' - '@opentelemetry/sdk-node@0.217.0': - resolution: {integrity: sha512-K/60pSv42+NQiZKy1pAH18nYDkxltsDV4O3SJ233J0E9raU1ksyL9gsKuS8p30bYBb4AMPCfDuutHQaHYpcv0Q==} + '@opentelemetry/sdk-node@0.218.0': + resolution: {integrity: sha512-tPMjHrLV5gsfNdYqoRHjeGbCAZBXXD9c1Qo/2ut7VwnUABDNh76xNxrT0SEhkIIJuCN45bbN1vZnYL1gY0IkOg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' @@ -3901,8 +4054,8 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/semantic-conventions@1.40.0': - resolution: {integrity: sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==} + '@opentelemetry/semantic-conventions@1.41.1': + resolution: {integrity: sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==} engines: {node: '>=14'} '@opentelemetry/sql-common@0.41.2': @@ -3911,8 +4064,8 @@ packages: peerDependencies: '@opentelemetry/api': ^1.1.0 - '@oxc-project/types@0.127.0': - resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} + '@oxc-project/types@0.130.0': + resolution: {integrity: sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==} '@paralleldrive/cuid2@2.3.1': resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==} @@ -4005,6 +4158,43 @@ packages: resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==} engines: {node: '>= 10.0.0'} + '@peculiar/asn1-cms@2.7.0': + resolution: {integrity: sha512-hew63shtzzvBcSHbhm+cyAmKe6AIfinT9hzEqSPjDC6opTTMKmTkQ0gHuN2KsWlvqiKw1S/fS94fhag/FJkioQ==} + + '@peculiar/asn1-csr@2.7.0': + resolution: {integrity: sha512-VVsAyGqErT9D1SY4aEqozThXMVI+ssVRiv2DDeYuvpBKLIgZ3hYs3Ay3u/VSoKq6ESFi9cf6rf3IOOzfwh7oMA==} + + '@peculiar/asn1-ecc@2.7.0': + resolution: {integrity: sha512-n7KEs/Q/wrB415cxy4fHOBhegp4NdJ15fkJPwcB/3/8iNBQC2L/N7SChJPKDJPZGYH0jD4Tg4/0vnHmwghnbKw==} + + '@peculiar/asn1-pfx@2.7.0': + resolution: {integrity: sha512-V/nrlQVmhg7lYAsM7E13UDL5erAwFv6kCIVFqNaMIHSVi7dngcT839JkRTkQBqznMG98l2XjxYk74ZztAohZzA==} + + '@peculiar/asn1-pkcs8@2.7.0': + resolution: {integrity: sha512-9GTl1nE8Mx1kTZ+7QyYatDyKsm34QcWRBFkY1iPvWC3X4Dona5s/tlLiQsx5WzVdZqiMBZNYT0buyw4/vbhnjw==} + + '@peculiar/asn1-pkcs9@2.7.0': + resolution: {integrity: sha512-Bh7m+OuIaSEllPQcSd9OSp93F4ROWH7sbITWV8MI+8dwsjE5111/87VxiWVvYFKyww3vp39geLv9ENqhwWHcew==} + + '@peculiar/asn1-rsa@2.7.0': + resolution: {integrity: sha512-/qvENQrXyTZURjMqSeofHul0JJt2sNSzSwk36pl2olkHbaioMQgrASDZAlHXl0xUlnVbHj0uGgOrBMTb5x2aJQ==} + + '@peculiar/asn1-schema@2.7.0': + resolution: {integrity: sha512-W8ZfWzLmQnrcky+eh3tni4IozMdqBDiHWU0N+vve/UGjMaUs8c0L7A2oEdkBXS8rTpWDpK/aoI3DG/L/hxmxPg==} + + '@peculiar/asn1-x509-attr@2.7.0': + resolution: {integrity: sha512-NS8e7SOgXipkzUPLF/sce7ukpMpWjhxYsH0n6Y+bHYo4TTxOb95Zv7hqwSuL212mj5YxovjdOKQOgH1As3E94w==} + + '@peculiar/asn1-x509@2.7.0': + resolution: {integrity: sha512-mUn9RRrkGDnG4ALfunDmzyRW5dg+sWCj/pfnCCqEHYbkGxEpvUt6iVJv8Yw1cyp6SWZ26ZE5oSmI5SqEaen15g==} + + '@peculiar/utils@2.0.3': + resolution: {integrity: sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==} + + '@peculiar/x509@1.14.3': + resolution: {integrity: sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==} + engines: {node: '>=20.0.0'} + '@photo-sphere-viewer/core@5.14.1': resolution: {integrity: sha512-qrwUudrX9YZms4c2shlY/H3jUP0oh9FyGEqIDr/95ulNZgKbhQ6C/i8zDQ4j8ooFR4+z5FDORQtGvLgPyX8VCA==} @@ -4046,8 +4236,8 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@playwright/test@1.59.1': - resolution: {integrity: sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==} + '@playwright/test@1.60.0': + resolution: {integrity: sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==} engines: {node: '>=18'} hasBin: true @@ -4059,8 +4249,8 @@ packages: resolution: {integrity: sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==} engines: {node: '>=12.22.0'} - '@pnpm/npm-conf@2.3.1': - resolution: {integrity: sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==} + '@pnpm/npm-conf@3.0.2': + resolution: {integrity: sha512-h104Kh26rR8tm+a3Qkc5S4VLYint3FE48as7+/5oCEcKR2idC/pF1G6AhIXKI+eHPJa/3J9i5z0Al47IeGHPkA==} engines: {node: '>=12'} '@polka/url@1.0.0-next.29': @@ -4072,20 +4262,20 @@ packages: '@protobufjs/base64@1.1.2': resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} - '@protobufjs/codegen@2.0.4': - resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + '@protobufjs/codegen@2.0.5': + resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} '@protobufjs/eventemitter@1.1.0': resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} - '@protobufjs/fetch@1.1.0': - resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + '@protobufjs/fetch@1.1.1': + resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} '@protobufjs/float@1.0.2': resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} - '@protobufjs/inquire@1.1.0': - resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + '@protobufjs/inquire@1.1.2': + resolution: {integrity: sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==} '@protobufjs/path@1.1.2': resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} @@ -4093,8 +4283,8 @@ packages: '@protobufjs/pool@1.1.0': resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} - '@protobufjs/utf8@1.1.0': - resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + '@protobufjs/utf8@1.1.1': + resolution: {integrity: sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==} '@react-email/body@0.3.0': resolution: {integrity: sha512-uGo0BOOzjbMUo3lu+BIDWayvn5o6Xyfmnlla5VGf05n8gHMvO1ll7U4FtzWe3hxMLwt53pmc4iE0M+B5slG+Ug==} @@ -4215,8 +4405,8 @@ packages: react: ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/render@2.0.7': - resolution: {integrity: sha512-XCsqujKURb4egU8+z7RX1/yxRx1Qo89uGhy6UXyB5Oxq1SK+48t0AD/3qeuDGgDvyS+Ti+0oDT3nn5/dcG4Ttg==} + '@react-email/render@2.0.8': + resolution: {integrity: sha512-5udvVr3U/WuGJZfLdLBOhkzrqRWd2Q5ZYmF7ppcy7FzWcwgshdqLMNqJOXcVzAXJXg/2bm7D+WGJzTtZOZMQnQ==} engines: {node: '>=20.0.0'} peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc @@ -4289,103 +4479,103 @@ packages: '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 - '@rolldown/binding-android-arm64@1.0.0-rc.17': - resolution: {integrity: sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==} + '@rolldown/binding-android-arm64@1.0.1': + resolution: {integrity: sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.0-rc.17': - resolution: {integrity: sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==} + '@rolldown/binding-darwin-arm64@1.0.1': + resolution: {integrity: sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-rc.17': - resolution: {integrity: sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==} + '@rolldown/binding-darwin-x64@1.0.1': + resolution: {integrity: sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.0-rc.17': - resolution: {integrity: sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==} + '@rolldown/binding-freebsd-x64@1.0.1': + resolution: {integrity: sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': - resolution: {integrity: sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.1': + resolution: {integrity: sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': - resolution: {integrity: sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==} + '@rolldown/binding-linux-arm64-gnu@1.0.1': + resolution: {integrity: sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': - resolution: {integrity: sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==} + '@rolldown/binding-linux-arm64-musl@1.0.1': + resolution: {integrity: sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': - resolution: {integrity: sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==} + '@rolldown/binding-linux-ppc64-gnu@1.0.1': + resolution: {integrity: sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': - resolution: {integrity: sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==} + '@rolldown/binding-linux-s390x-gnu@1.0.1': + resolution: {integrity: sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': - resolution: {integrity: sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==} + '@rolldown/binding-linux-x64-gnu@1.0.1': + resolution: {integrity: sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': - resolution: {integrity: sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==} + '@rolldown/binding-linux-x64-musl@1.0.1': + resolution: {integrity: sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': - resolution: {integrity: sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==} + '@rolldown/binding-openharmony-arm64@1.0.1': + resolution: {integrity: sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': - resolution: {integrity: sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==} + '@rolldown/binding-wasm32-wasi@1.0.1': + resolution: {integrity: sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': - resolution: {integrity: sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==} + '@rolldown/binding-win32-arm64-msvc@1.0.1': + resolution: {integrity: sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': - resolution: {integrity: sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==} + '@rolldown/binding-win32-x64-msvc@1.0.1': + resolution: {integrity: sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@rolldown/pluginutils@1.0.0-rc.17': - resolution: {integrity: sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==} + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} '@rollup/pluginutils@5.3.0': resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} @@ -4396,141 +4586,141 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.55.1': - resolution: {integrity: sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==} + '@rollup/rollup-android-arm-eabi@4.60.4': + resolution: {integrity: sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.55.1': - resolution: {integrity: sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==} + '@rollup/rollup-android-arm64@4.60.4': + resolution: {integrity: sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.55.1': - resolution: {integrity: sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==} + '@rollup/rollup-darwin-arm64@4.60.4': + resolution: {integrity: sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.55.1': - resolution: {integrity: sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==} + '@rollup/rollup-darwin-x64@4.60.4': + resolution: {integrity: sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.55.1': - resolution: {integrity: sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==} + '@rollup/rollup-freebsd-arm64@4.60.4': + resolution: {integrity: sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.55.1': - resolution: {integrity: sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==} + '@rollup/rollup-freebsd-x64@4.60.4': + resolution: {integrity: sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.55.1': - resolution: {integrity: sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==} + '@rollup/rollup-linux-arm-gnueabihf@4.60.4': + resolution: {integrity: sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==} cpu: [arm] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.55.1': - resolution: {integrity: sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==} + '@rollup/rollup-linux-arm-musleabihf@4.60.4': + resolution: {integrity: sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==} cpu: [arm] os: [linux] libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.55.1': - resolution: {integrity: sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==} + '@rollup/rollup-linux-arm64-gnu@4.60.4': + resolution: {integrity: sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==} cpu: [arm64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.55.1': - resolution: {integrity: sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==} + '@rollup/rollup-linux-arm64-musl@4.60.4': + resolution: {integrity: sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==} cpu: [arm64] os: [linux] libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.55.1': - resolution: {integrity: sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==} + '@rollup/rollup-linux-loong64-gnu@4.60.4': + resolution: {integrity: sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==} cpu: [loong64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-loong64-musl@4.55.1': - resolution: {integrity: sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==} + '@rollup/rollup-linux-loong64-musl@4.60.4': + resolution: {integrity: sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==} cpu: [loong64] os: [linux] libc: [musl] - '@rollup/rollup-linux-ppc64-gnu@4.55.1': - resolution: {integrity: sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==} + '@rollup/rollup-linux-ppc64-gnu@4.60.4': + resolution: {integrity: sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==} cpu: [ppc64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-ppc64-musl@4.55.1': - resolution: {integrity: sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==} + '@rollup/rollup-linux-ppc64-musl@4.60.4': + resolution: {integrity: sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==} cpu: [ppc64] os: [linux] libc: [musl] - '@rollup/rollup-linux-riscv64-gnu@4.55.1': - resolution: {integrity: sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==} + '@rollup/rollup-linux-riscv64-gnu@4.60.4': + resolution: {integrity: sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==} cpu: [riscv64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.55.1': - resolution: {integrity: sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==} + '@rollup/rollup-linux-riscv64-musl@4.60.4': + resolution: {integrity: sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==} cpu: [riscv64] os: [linux] libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.55.1': - resolution: {integrity: sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==} + '@rollup/rollup-linux-s390x-gnu@4.60.4': + resolution: {integrity: sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==} cpu: [s390x] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.55.1': - resolution: {integrity: sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==} + '@rollup/rollup-linux-x64-gnu@4.60.4': + resolution: {integrity: sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==} cpu: [x64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.55.1': - resolution: {integrity: sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==} + '@rollup/rollup-linux-x64-musl@4.60.4': + resolution: {integrity: sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==} cpu: [x64] os: [linux] libc: [musl] - '@rollup/rollup-openbsd-x64@4.55.1': - resolution: {integrity: sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==} + '@rollup/rollup-openbsd-x64@4.60.4': + resolution: {integrity: sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==} cpu: [x64] os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.55.1': - resolution: {integrity: sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==} + '@rollup/rollup-openharmony-arm64@4.60.4': + resolution: {integrity: sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.55.1': - resolution: {integrity: sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==} + '@rollup/rollup-win32-arm64-msvc@4.60.4': + resolution: {integrity: sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.55.1': - resolution: {integrity: sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==} + '@rollup/rollup-win32-ia32-msvc@4.60.4': + resolution: {integrity: sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.55.1': - resolution: {integrity: sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==} + '@rollup/rollup-win32-x64-gnu@4.60.4': + resolution: {integrity: sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.55.1': - resolution: {integrity: sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==} + '@rollup/rollup-win32-x64-msvc@4.60.4': + resolution: {integrity: sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==} cpu: [x64] os: [win32] @@ -4549,8 +4739,8 @@ packages: '@sideway/pinpoint@2.0.0': resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} - '@sinclair/typebox@0.27.8': - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + '@sinclair/typebox@0.27.10': + resolution: {integrity: sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==} '@sindresorhus/is@4.6.0': resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} @@ -4584,8 +4774,8 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@sveltejs/acorn-typescript@1.0.9': - resolution: {integrity: sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==} + '@sveltejs/acorn-typescript@1.0.10': + resolution: {integrity: sha512-4WfKk68eTih+MiJD4fSbxN7E8kVBmTMPWHUPYjvl2N0rMs53YLTT8/YjKU5Dtnz5LqDjl7LEw4U7lXR2W3J5WA==} peerDependencies: acorn: ^8.9.0 @@ -4601,8 +4791,8 @@ packages: svelte: ^5.0.0 vite: ^6.3.0 || >=7.0.0 - '@sveltejs/kit@2.57.1': - resolution: {integrity: sha512-VRdSbB96cI1EnRh09CqmnQqP/YJvET5buj8S6k7CxaJqBJD4bw4fRKDjcarAj/eX9k2eHifQfDH8NtOh+ZxxPw==} + '@sveltejs/kit@2.60.1': + resolution: {integrity: sha512-mQjlkNo+rJvpln7V2IGY2j99BqhcFbS4UN0AQNKNYfhBAFZTuCDAdW3a1sgf330mvtNvsBXn3HpAhcmvdJTcIQ==} engines: {node: '>=18.13'} hasBin: true peerDependencies: @@ -4617,8 +4807,8 @@ packages: typescript: optional: true - '@sveltejs/vite-plugin-svelte@7.0.0': - resolution: {integrity: sha512-ILXmxC7HAsnkK2eslgPetrqqW1BKSL7LktsFgqzNj83MaivMGZzluWq32m25j2mDOjmSKX7GGWahePhuEs7P/g==} + '@sveltejs/vite-plugin-svelte@7.1.2': + resolution: {integrity: sha512-DrUBA2UXRfDmUX/ZTiEopd3X40yavsJF1FX2RygcuIScHL7o5YX1fMvoYnDhjeJQC4weCOklirpNWlcb2NiSeA==} engines: {node: ^20.19 || ^22.12 || >=24} peerDependencies: svelte: ^5.46.4 @@ -4702,86 +4892,86 @@ packages: resolution: {integrity: sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==} engines: {node: '>=14'} - '@swc/core-darwin-arm64@1.15.30': - resolution: {integrity: sha512-VvpP+vq08HmGYewMWvrdsxh9s2lthz/808zXm8Yu5kaqeR8Yia2b0eYXleHQ3VAjoStUDk6LzTheBW9KXYQdMA==} + '@swc/core-darwin-arm64@1.15.33': + resolution: {integrity: sha512-N+L0uXhuO7FIfzqwgxmzv0zIpV0qEp8wPX3QQs2p4atjMoywup2JTeDlXPw+z9pWJGCae3JjM+tZ6myclI+2gA==} engines: {node: '>=10'} cpu: [arm64] os: [darwin] - '@swc/core-darwin-x64@1.15.30': - resolution: {integrity: sha512-WiJA0hiZI3nwQAO6mu5RqigtWGDtth4Hiq6rbZxAaQyhIcqKIg5IoMRc1Y071lrNJn29eEDMC86Rq58xgUxlDg==} + '@swc/core-darwin-x64@1.15.33': + resolution: {integrity: sha512-/Il4QHSOhV4FekbsDtkrNmKbsX26oSysvgrRswa/RYOHXAkwXDbB4jaeKq6PsJLSPkzJ2KzQ061gtBnk0vNHfA==} engines: {node: '>=10'} cpu: [x64] os: [darwin] - '@swc/core-linux-arm-gnueabihf@1.15.30': - resolution: {integrity: sha512-YANuFUo48kIT6plJgCD0keae9HFXfjxsbvsgevqc0hr/07X/p7sAWTFOGYEc2SXcASaK7UvuQqzlbW8pr7R79g==} + '@swc/core-linux-arm-gnueabihf@1.15.33': + resolution: {integrity: sha512-C64hBnBxq4viOPQ8hlx+2lJ23bzZBGnjw7ryALmS+0Q3zHmwO8lw1/DArLENw4Q18/0w5wdEO1k3m1wWNtKGqQ==} engines: {node: '>=10'} cpu: [arm] os: [linux] - '@swc/core-linux-arm64-gnu@1.15.30': - resolution: {integrity: sha512-VndG8jaR4ugY6u+iVOT0Q+d2fZd7sLgjPgN8W/Le+3EbZKl+cRfFxV7Eoz4gfLqhmneZPdcIzf9T3LkgkmqNLg==} + '@swc/core-linux-arm64-gnu@1.15.33': + resolution: {integrity: sha512-TRJfnJbX3jqpxRDRoieMzRiCBS5jOmXNb3iQXmcgjFEHKLnAgK1RZRU8Cq1MsPqO4jAJp/ld1G4O3fXuxv85uw==} engines: {node: '>=10'} cpu: [arm64] os: [linux] libc: [glibc] - '@swc/core-linux-arm64-musl@1.15.30': - resolution: {integrity: sha512-1SYGs2l0Yyyi0pR/P/NKz/x0kqxkoiw+BXeJjLUdecSk/KasncWlJrc6hOvFSgKHOBrzgM5jwuluKtlT8dnrcA==} + '@swc/core-linux-arm64-musl@1.15.33': + resolution: {integrity: sha512-il7tYM+CpUNzieQbwAjFT1P8zqAhmGWNAGhQZBnxurXZ0aNn+5nqYFTEUKNZl7QibtT0uQXzTZrNGHCIj6Y1Og==} engines: {node: '>=10'} cpu: [arm64] os: [linux] libc: [musl] - '@swc/core-linux-ppc64-gnu@1.15.30': - resolution: {integrity: sha512-TXREtiXeRhbfDFbmhnkIsXpKfzbfT73YkV2ZF6w0sfxgjC5zI2ZAbaCOq25qxvegofj2K93DtOpm9RLaBgqR2g==} + '@swc/core-linux-ppc64-gnu@1.15.33': + resolution: {integrity: sha512-ZtNBwN0Z7CFj9Il0FcPaKdjgP7URyKu/3RfH46vq+0paOBqLj4NYldD6Qo//Duif/7IOtAraUfDOmp0PLAufog==} engines: {node: '>=10'} cpu: [ppc64] os: [linux] libc: [glibc] - '@swc/core-linux-s390x-gnu@1.15.30': - resolution: {integrity: sha512-DCR2YYeyd6DQE4OuDhImouuNcjXEiEdnn1Y0DyGteugPEDvVuvYk8Xddi+4o2SgWH6jiW8/I+3emZvbep1NC+g==} + '@swc/core-linux-s390x-gnu@1.15.33': + resolution: {integrity: sha512-De1IyajoOmhOYYjw/lx66bKlyDpHZTueqwpDrWgf5O7T6d1ODeJJO9/OqMBmrBQc5C+dNnlmIufHsp4QVCWufA==} engines: {node: '>=10'} cpu: [s390x] os: [linux] libc: [glibc] - '@swc/core-linux-x64-gnu@1.15.30': - resolution: {integrity: sha512-5Pizw3NgfOJ5BJOBK8TIRa59xFW2avESTOBDPTAYwZYa1JNDs+KMF9lUfjJiJLM5HiMs/wPheA9eiT0q9m2AoA==} + '@swc/core-linux-x64-gnu@1.15.33': + resolution: {integrity: sha512-mGTH0YxmUN+x6vRN/I6NOk5X0ogNktkwPnJ94IMvR7QjhRDwL0O8RXEDhyUM0YtwWrryBOqaJQBX4zruxEPRGw==} engines: {node: '>=10'} cpu: [x64] os: [linux] libc: [glibc] - '@swc/core-linux-x64-musl@1.15.30': - resolution: {integrity: sha512-qyqydP/wyH8alcIP4a2hnGSjHLJjm9H7yDFup+CPy9oTahFgLLwnNcv5UHXqO2Qs3AIND+cls5f/Bb6hqpxdgA==} + '@swc/core-linux-x64-musl@1.15.33': + resolution: {integrity: sha512-hj628ZkSEJf6zMf5VMbYrG2O6QqyTIp2qwY6VlCjvIa9lAEZ5c2lfPblCLVGYubTeLJDxadLB/CxqQYOQABeEQ==} engines: {node: '>=10'} cpu: [x64] os: [linux] libc: [musl] - '@swc/core-win32-arm64-msvc@1.15.30': - resolution: {integrity: sha512-CaQENgDHVGOg1mSF5sQVgvfFHG9kjMor2rkLMLeLOkfZYNj13ppnJ9+lfaBZLZUMMbnlGQnavCJb8PVBUOso7Q==} + '@swc/core-win32-arm64-msvc@1.15.33': + resolution: {integrity: sha512-GV2oohtN2/5+KSccl86VULu3aT+LrISC8uzgSq0FRnikpD+Zwc+sBlXmoKQ+Db6jI57ITUOIB8jRkdGMABC29g==} engines: {node: '>=10'} cpu: [arm64] os: [win32] - '@swc/core-win32-ia32-msvc@1.15.30': - resolution: {integrity: sha512-30VdLeGk6fugiUs/kUdJ/pAg7z/zpvVbR11RH60jZ0Z42WIeIniYx0rLEWN7h/pKJ3CopqsQ3RsogCAkRKiA2g==} + '@swc/core-win32-ia32-msvc@1.15.33': + resolution: {integrity: sha512-gtyvzSNR8DHKfFEA2uqb8Ld1myqi6uEg2jyeUq3ikn5ytYs7H8RpZYC8mdy4NXr8hfcdJfCLXPlYaqqfBXpoEQ==} engines: {node: '>=10'} cpu: [ia32] os: [win32] - '@swc/core-win32-x64-msvc@1.15.30': - resolution: {integrity: sha512-4iObHPR+Q4oDY110EF5SF5eIaaVJNpMdG9C0q3Q92BsJ5y467uHz7sYQhP60WYlLFsLQ1el2YrIPUItUAQGOKg==} + '@swc/core-win32-x64-msvc@1.15.33': + resolution: {integrity: sha512-d6fRqQSkJI+kmMEBWaDQ7TMl8+YjLYbwRUPZQ9DY0ORBJeTzOrG0twvfvlZ2xgw6jA0ScQKgfBm4vHLSLl5Hqg==} engines: {node: '>=10'} cpu: [x64] os: [win32] - '@swc/core@1.15.30': - resolution: {integrity: sha512-R8VQbQY1BZcbIF2p3gjlTCwAQzx1A194ugWfwld5y+WgVVWqVKm7eURGGOVbQVubgKWzidP2agomBbg96rZilQ==} + '@swc/core@1.15.33': + resolution: {integrity: sha512-jOlwnFV2xhuuZeAUILGFULeR6vDPfijEJ57evfocwznQldLU3w2cZ9bSDryY9ip+AsM3r1NJKzf47V2NXebkeQ==} engines: {node: '>=10'} peerDependencies: '@swc/helpers': '>=0.5.17' @@ -4802,69 +4992,69 @@ packages: resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} engines: {node: '>=14.16'} - '@tailwindcss/node@4.2.4': - resolution: {integrity: sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==} + '@tailwindcss/node@4.3.0': + resolution: {integrity: sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==} - '@tailwindcss/oxide-android-arm64@4.2.4': - resolution: {integrity: sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==} + '@tailwindcss/oxide-android-arm64@4.3.0': + resolution: {integrity: sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==} engines: {node: '>= 20'} cpu: [arm64] os: [android] - '@tailwindcss/oxide-darwin-arm64@4.2.4': - resolution: {integrity: sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==} + '@tailwindcss/oxide-darwin-arm64@4.3.0': + resolution: {integrity: sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==} engines: {node: '>= 20'} cpu: [arm64] os: [darwin] - '@tailwindcss/oxide-darwin-x64@4.2.4': - resolution: {integrity: sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==} + '@tailwindcss/oxide-darwin-x64@4.3.0': + resolution: {integrity: sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==} engines: {node: '>= 20'} cpu: [x64] os: [darwin] - '@tailwindcss/oxide-freebsd-x64@4.2.4': - resolution: {integrity: sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==} + '@tailwindcss/oxide-freebsd-x64@4.3.0': + resolution: {integrity: sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==} engines: {node: '>= 20'} cpu: [x64] os: [freebsd] - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': - resolution: {integrity: sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==} + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0': + resolution: {integrity: sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==} engines: {node: '>= 20'} cpu: [arm] os: [linux] - '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': - resolution: {integrity: sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==} + '@tailwindcss/oxide-linux-arm64-gnu@4.3.0': + resolution: {integrity: sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] libc: [glibc] - '@tailwindcss/oxide-linux-arm64-musl@4.2.4': - resolution: {integrity: sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==} + '@tailwindcss/oxide-linux-arm64-musl@4.3.0': + resolution: {integrity: sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] libc: [musl] - '@tailwindcss/oxide-linux-x64-gnu@4.2.4': - resolution: {integrity: sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==} + '@tailwindcss/oxide-linux-x64-gnu@4.3.0': + resolution: {integrity: sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==} engines: {node: '>= 20'} cpu: [x64] os: [linux] libc: [glibc] - '@tailwindcss/oxide-linux-x64-musl@4.2.4': - resolution: {integrity: sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==} + '@tailwindcss/oxide-linux-x64-musl@4.3.0': + resolution: {integrity: sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==} engines: {node: '>= 20'} cpu: [x64] os: [linux] libc: [musl] - '@tailwindcss/oxide-wasm32-wasi@4.2.4': - resolution: {integrity: sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==} + '@tailwindcss/oxide-wasm32-wasi@4.3.0': + resolution: {integrity: sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==} engines: {node: '>=14.0.0'} cpu: [wasm32] bundledDependencies: @@ -4875,24 +5065,24 @@ packages: - '@emnapi/wasi-threads' - tslib - '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': - resolution: {integrity: sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==} + '@tailwindcss/oxide-win32-arm64-msvc@4.3.0': + resolution: {integrity: sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==} engines: {node: '>= 20'} cpu: [arm64] os: [win32] - '@tailwindcss/oxide-win32-x64-msvc@4.2.4': - resolution: {integrity: sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==} + '@tailwindcss/oxide-win32-x64-msvc@4.3.0': + resolution: {integrity: sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==} engines: {node: '>= 20'} cpu: [x64] os: [win32] - '@tailwindcss/oxide@4.2.4': - resolution: {integrity: sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==} + '@tailwindcss/oxide@4.3.0': + resolution: {integrity: sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==} engines: {node: '>= 20'} - '@tailwindcss/vite@4.2.4': - resolution: {integrity: sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw==} + '@tailwindcss/vite@4.3.0': + resolution: {integrity: sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==} peerDependencies: vite: ^5.2.0 || ^6 || ^7 || ^8 @@ -4955,21 +5145,17 @@ packages: svelte: optional: true - '@trysound/sax@0.2.0': - resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} - engines: {node: '>=10.13.0'} + '@turf/boolean-point-in-polygon@7.3.5': + resolution: {integrity: sha512-ba7+B0wzaS9GtERZOoXUZ6oW8IcIJHNQZf3c+tiD9ESjcsPO1Q/4qIJGTKl92nBLhhracHJxMWBM/U6hAVkaRg==} - '@turf/boolean-point-in-polygon@7.3.4': - resolution: {integrity: sha512-v/4hfyY90Vz9cDgs2GwjQf+Lft8o7mNCLJOTz/iv8SHAIgMMX0czEoIaNVOJr7tBqPqwin1CGwsncrkf5C9n8Q==} + '@turf/helpers@7.3.5': + resolution: {integrity: sha512-E/NMGV5MwbjjP7AJXBtsanC3yY8N2MQ87IGdIgkB2ji5AtBpwnH4L3gEqpYN4RlCJJWbLbzO91BbKv2waUd0eg==} - '@turf/helpers@7.3.4': - resolution: {integrity: sha512-U/S5qyqgx3WTvg4twaH0WxF3EixoTCfDsmk98g1E3/5e2YKp7JKYZdz0vivsS5/UZLJeZDEElOSFH4pUgp+l7g==} + '@turf/invariant@7.3.5': + resolution: {integrity: sha512-ZVIvsBvjr8lO7WxC5zYNjRsjSDvyGvWkJMjuWaJjTU8x+1tmfNnw3gDX/TI2Sit83gcRYLYkNo23lB/udqx/Hg==} - '@turf/invariant@7.3.4': - resolution: {integrity: sha512-88Eo4va4rce9sNZs6XiMJowWkikM3cS2TBhaCKlU+GFHdNf8PFEpiU42VDU8q5tOF6/fu21Rvlke5odgOGW4AQ==} - - '@tybys/wasm-util@0.10.1': - resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@tybys/wasm-util@0.10.2': + resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} '@types/accepts@1.3.7': resolution: {integrity: sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==} @@ -5001,8 +5187,8 @@ packages: '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} - '@types/chrome@0.1.32': - resolution: {integrity: sha512-n5Cqlh7zyAqRLQWLXkeV5K/1BgDZdVcO/dJSTa8x+7w+sx7m73UrDmduAptg4KorMtyTW2TNnPu8RGeaDMKNGg==} + '@types/chrome@0.1.42': + resolution: {integrity: sha512-tdT2roFqGecZZDjA9fUEAINb2STxSPifHMDvY6EfRjNRCjdrs/0FwKt5RCIA9MKMd1arAYZZL3nwEkp6ZLZu2w==} '@types/chromecast-caf-sender@1.0.11': resolution: {integrity: sha512-Pv3xvNYtxD/cTM/tKfuZRlLasvpxAm+CFni0GJd6Cp8XgiZS9g9tMZkR1uymsi5fIFv057SZKKAWVFFgy7fJtw==} @@ -5132,8 +5318,8 @@ packages: '@types/d3@7.4.3': resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} - '@types/debug@4.1.12': - resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} @@ -5162,11 +5348,14 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - '@types/express-serve-static-core@4.19.7': - resolution: {integrity: sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==} + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} - '@types/express-serve-static-core@5.1.0': - resolution: {integrity: sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==} + '@types/express-serve-static-core@4.19.8': + resolution: {integrity: sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==} + + '@types/express-serve-static-core@5.1.1': + resolution: {integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==} '@types/express@4.17.25': resolution: {integrity: sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==} @@ -5207,8 +5396,8 @@ packages: '@types/http-assert@1.5.6': resolution: {integrity: sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw==} - '@types/http-cache-semantics@4.0.4': - resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} + '@types/http-cache-semantics@4.2.0': + resolution: {integrity: sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==} '@types/http-errors@2.0.5': resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} @@ -5246,8 +5435,8 @@ packages: '@types/koa-compose@3.2.9': resolution: {integrity: sha512-BroAZ9FTvPiCy0Pi8tjD1OfJ7bgU1gQf0eR6e1Vm+JJATy9eKOG3hQMFtMciMawiSOVnLMdmUOC46s7HBhSTsA==} - '@types/koa@3.0.1': - resolution: {integrity: sha512-VkB6WJUQSe0zBpR+Q7/YIUESGp5wPHcaXr0xueU5W0EOUWtlSbblsl+Kl31lyRQ63nIILh0e/7gXjQ09JXJIHw==} + '@types/koa@3.0.3': + resolution: {integrity: sha512-TdtNEJ7sYSrFQcVuS2ySsVqnq5EyE3oJbnfFJvkC9UtGP4Kpem5KE7r+ivHIbIAQAofSqnlB5D3vkfYO69TQpg==} '@types/leaflet@1.9.21': resolution: {integrity: sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==} @@ -5285,20 +5474,14 @@ packages: '@types/multer@2.1.0': resolution: {integrity: sha512-zYZb0+nJhOHtPpGDb3vqPjwpdeGlGC157VpkqNQL+UU2qwoacoQ7MpsAmUptI/0Oa127X32JzWDqQVEXp2RcIA==} - '@types/node-forge@1.3.14': - resolution: {integrity: sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==} - '@types/node@17.0.45': resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} '@types/node@18.19.130': resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} - '@types/node@24.12.2': - resolution: {integrity: sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==} - - '@types/node@25.6.0': - resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==} + '@types/node@24.12.4': + resolution: {integrity: sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==} '@types/nodemailer@8.0.0': resolution: {integrity: sha512-fyf8jWULsCo0d0BuoQ75i6IeoHs47qcqxWc7yUdUcV0pOZGjUTTOvwdG1PRXUDqN/8A64yQdQdnA2pZgcdi+cA==} @@ -5324,14 +5507,14 @@ packages: '@types/pngjs@6.0.5': resolution: {integrity: sha512-0k5eKfrA83JOZPppLtS2C7OUtyNAl2wKNxfyYl9Q5g9lPkgBl/9hNyAu6HuEH2J4XmIv2znEpkDd0SaZVxW6iQ==} - '@types/prismjs@1.26.5': - resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==} + '@types/prismjs@1.26.6': + resolution: {integrity: sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==} '@types/qrcode@1.5.6': resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==} - '@types/qs@6.14.0': - resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} + '@types/qs@6.15.1': + resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==} '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} @@ -5345,8 +5528,8 @@ packages: '@types/react-router@5.1.20': resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==} - '@types/react@19.2.14': - resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + '@types/react@19.2.15': + resolution: {integrity: sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==} '@types/readdir-glob@1.1.5': resolution: {integrity: sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==} @@ -5387,8 +5570,8 @@ packages: '@types/ssh2@1.15.5': resolution: {integrity: sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ==} - '@types/superagent@8.1.9': - resolution: {integrity: sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==} + '@types/superagent@8.1.10': + resolution: {integrity: sha512-nbt4IWXABhW0jGmmpRzCFNlbmwCTzZ2gTUsNIr+X+ItdqPms+PAJZbWsNzpS2USqXjcoNLQcO6nXo60zcPQiIg==} '@types/supercluster@7.1.3': resolution: {integrity: sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==} @@ -5426,77 +5609,75 @@ packages: '@types/yargs@17.0.35': resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} - '@typescript-eslint/eslint-plugin@8.59.0': - resolution: {integrity: sha512-HyAZtpdkgZwpq8Sz3FSUvCR4c+ScbuWa9AksK2Jweub7w4M3yTz4O11AqVJzLYjy/B9ZWPyc81I+mOdJU/bDQw==} + '@typescript-eslint/eslint-plugin@8.59.4': + resolution: {integrity: sha512-PegsU+XfyJJNjd4+u/k6f9yTyp0lEXXiPopUNobZcIAUJFGICFLN+sP0Rb3JehVmiij1Ph0dFGYqODoRo/2+6A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.59.0 + '@typescript-eslint/parser': ^8.59.4 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.59.0': - resolution: {integrity: sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg==} + '@typescript-eslint/parser@8.59.4': + resolution: {integrity: sha512-zORHqO/tuhxY1zWuTvMUqddRxpiFJ72xVfcNoWpqdLjs6lfPbuQBJuW4pk+49/uBMy7Ssr4bzgjiKmmDB1UbZQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.59.0': - resolution: {integrity: sha512-Lw5ITrR5s5TbC19YSvlr63ZfLaJoU6vtKTHyB0GQOpX0W7d5/Ir6vUahWi/8Sps/nOukZQ0IB3SmlxZnjaKVnw==} + '@typescript-eslint/project-service@8.59.4': + resolution: {integrity: sha512-Ly00Vu4oAacfDeHp2Zg85ioNG6l8HG+tN1D7J+xTHSxu9y0awYKJ2zH1rFBn8ZSfuGK+7FxK3Cgl3uAz0aZZLg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.59.0': - resolution: {integrity: sha512-UzR16Ut8IpA3Mc4DbgAShlPPkVm8xXMWafXxB0BocaVRHs8ZGakAxGRskF7FId3sdk9lgGD73GSFaWmWFDE4dg==} + '@typescript-eslint/scope-manager@8.59.4': + resolution: {integrity: sha512-mUeR/3H1WrTAddJrwut8OoPjfauaztMQmRwV5fQTUyNVJCLiUXXe4lGEyYIL2oFDpP7UtgbGJXCt72wT0z2S3Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.59.0': - resolution: {integrity: sha512-91Sbl3s4Kb3SybliIY6muFBmHVv+pYXfybC4Oolp3dvk8BvIE3wOPc+403CWIT7mJNkfQRGtdqghzs2+Z91Tqg==} + '@typescript-eslint/tsconfig-utils@8.59.4': + resolution: {integrity: sha512-DLCpnKgD4alVxTBSKulK+gU1KCqOgUXfDRDXh2mZgzokQKa/70ax93I2uVO3m/LLvIAtWZIFoiifudmIqAxpMA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.59.0': - resolution: {integrity: sha512-3TRiZaQSltGqGeNrJzzr1+8YcEobKH9rHnqIp/1psfKFmhRQDNMGP5hBufanYTGznwShzVLs3Mz+gDN7HkWfXg==} + '@typescript-eslint/type-utils@8.59.4': + resolution: {integrity: sha512-uonTuPAAKr9XaBGqJ3LjYTh72zy5DyGesljO9gtmk/eFW0W1fRHjnwVYKB35Lm8d5Q5CluEW3gPHjTvZTmgrfA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.59.0': - resolution: {integrity: sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A==} + '@typescript-eslint/types@8.59.4': + resolution: {integrity: sha512-F1o7WJcCq+bc8dwcO/YsSEOudAH8RDtaOhM6wcAQhcUsFhnWQl81JKy48q1hoxAU0qrzM89+31GYh1515Zde3Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.59.0': - resolution: {integrity: sha512-O9Re9P1BmBLFJyikRbQpLku/QA3/AueZNO9WePLBwQrvkixTmDe8u76B6CYUAITRl/rHawggEqUGn5QIkVRLMw==} + '@typescript-eslint/typescript-estree@8.59.4': + resolution: {integrity: sha512-F+RuOmcDXo4+TPdfd/TCLS3m2nw8gE9XXyZLrA3JBfaA5tz9TtdkyD3YJFmPxulyc2cKbEok/CvFE3MgSLWnag==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.59.0': - resolution: {integrity: sha512-I1R/K7V07XsMJ12Oaxg/O9GfrysGTmCRhvZJBv0RE0NcULMzjqVpR5kRRQjHsz3J/bElU7HwCO7zkqL+MSUz+g==} + '@typescript-eslint/utils@8.59.4': + resolution: {integrity: sha512-cYXeNAUsG4lJo5dbc1FcKm+JwIWrj1/UpTORsC6tGMjEZ81DYcvIr9/ueikhMa/Y/gDQYGp+YX9/xQrXje5BJw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.59.0': - resolution: {integrity: sha512-/uejZt4dSere1bx12WLlPfv8GktzcaDtuJ7s42/HEZ5zGj9oxRaD4bj7qwSunXkf+pbAhFt2zjpHYUiT5lHf0Q==} + '@typescript-eslint/visitor-keys@8.59.4': + resolution: {integrity: sha512-U3gxVaDVnuZKhSspW/MzMxE1kq7zOdc072FcSNoqA1I9p8HyKbBFfEHoWckBAMgNMph4MamwS5iTVzFmrnt8TQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@ungap/structured-clone@1.3.0': - resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - deprecated: Potential CWE-502 - Update to 1.3.1 or higher + '@ungap/structured-clone@1.3.1': + resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==} - '@valibot/to-json-schema@1.6.0': - resolution: {integrity: sha512-d6rYyK5KVa2XdqamWgZ4/Nr+cXhxjy7lmpe6Iajw15J/jmU+gyxl2IEd1Otg1d7Rl3gOQL5reulnSypzBtYy1A==} + '@upsetjs/venn.js@2.0.0': + resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==} + + '@valibot/to-json-schema@1.7.0': + resolution: {integrity: sha512-Y3pPVibbIOHzohrlxSINvO7w/bvXkoYS3BQHoImV9ynE+bXKf171bdMucPurV2zp7gdmt0L1HCcNAsbo7cFRQw==} peerDependencies: - valibot: ^1.3.0 - - '@vercel/oidc@3.0.5': - resolution: {integrity: sha512-fnYhv671l+eTTp48gB4zEsTW/YtRgRPnkI2nT7x6qw5rkI1Lq2hTmQIpHPgyThI0znLK+vX2n9XxKdXZ7BUbbw==} - engines: {node: '>= 20'} + valibot: ^1.4.0 '@vitest/coverage-v8@3.2.4': resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==} @@ -5507,11 +5688,11 @@ packages: '@vitest/browser': optional: true - '@vitest/coverage-v8@4.1.5': - resolution: {integrity: sha512-38C0/Ddb7HcRG0Z4/DUem8x57d2p9jYgp18mkaYswEOQBGsI1CG4f/hjm0ZCeaJfWhSZ4k7jgs29V1Zom7Ki9A==} + '@vitest/coverage-v8@4.1.7': + resolution: {integrity: sha512-qsYPeXc5Q9dFLd1i8Ap+Bx8sQgcp+rFVQo4R0dDsWNBzl26ldVF1qOO+RL24K7FDrR6pA+50XedRLSoSG24bVQ==} peerDependencies: - '@vitest/browser': 4.1.5 - vitest: 4.1.5 + '@vitest/browser': 4.1.7 + vitest: 4.1.7 peerDependenciesMeta: '@vitest/browser': optional: true @@ -5519,8 +5700,8 @@ packages: '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} - '@vitest/expect@4.1.5': - resolution: {integrity: sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==} + '@vitest/expect@4.1.7': + resolution: {integrity: sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==} '@vitest/mocker@3.2.4': resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} @@ -5533,8 +5714,8 @@ packages: vite: optional: true - '@vitest/mocker@4.1.5': - resolution: {integrity: sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==} + '@vitest/mocker@4.1.7': + resolution: {integrity: sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -5547,32 +5728,32 @@ packages: '@vitest/pretty-format@3.2.4': resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} - '@vitest/pretty-format@4.1.5': - resolution: {integrity: sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==} + '@vitest/pretty-format@4.1.7': + resolution: {integrity: sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==} '@vitest/runner@3.2.4': resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} - '@vitest/runner@4.1.5': - resolution: {integrity: sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==} + '@vitest/runner@4.1.7': + resolution: {integrity: sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==} '@vitest/snapshot@3.2.4': resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} - '@vitest/snapshot@4.1.5': - resolution: {integrity: sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==} + '@vitest/snapshot@4.1.7': + resolution: {integrity: sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==} '@vitest/spy@3.2.4': resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} - '@vitest/spy@4.1.5': - resolution: {integrity: sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==} + '@vitest/spy@4.1.7': + resolution: {integrity: sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==} '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} - '@vitest/utils@4.1.5': - resolution: {integrity: sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==} + '@vitest/utils@4.1.7': + resolution: {integrity: sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==} '@webassemblyjs/ast@1.14.1': resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} @@ -5668,8 +5849,8 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn-walk@8.3.4: - resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + acorn-walk@8.3.5: + resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} engines: {node: '>=0.4.0'} acorn@8.16.0: @@ -5693,12 +5874,6 @@ packages: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} - ai@5.0.113: - resolution: {integrity: sha512-26vivpSO/mzZj0k1Si2IpsFspp26ttQICHRySQiMrtWcRd5mnJMX2a8sG28vmZ38C+JUn1cWmfZrsLMxkSMw9g==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: @@ -5725,19 +5900,22 @@ packages: peerDependencies: ajv: ^8.8.2 - ajv@6.14.0: - resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} ajv@8.18.0: resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} - algoliasearch-helper@3.26.1: - resolution: {integrity: sha512-CAlCxm4fYBXtvc5MamDzP6Svu8rW4z9me4DCBY1rQ2UDJ0u0flWmusQ8M3nOExZsLLRcUwUPoRAPMrhzOG3erw==} + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + + algoliasearch-helper@3.29.1: + resolution: {integrity: sha512-6ck2YFudF2Pje7szQoPBiRFTGfd+1I+0I/WfLPGn0bj1kvrFoOQmNyedNiDxTk3/r4IfSLDYk+RA4G7u8H6+yA==} peerDependencies: algoliasearch: '>= 3.1 < 6' - algoliasearch@5.46.0: - resolution: {integrity: sha512-7ML6fa2K93FIfifG3GMWhDEwT5qQzPTmoHKCTvhzGEwdbQ4n0yYUWZlLYT75WllTGJCJtNUI0C1ybN4BCegqvg==} + algoliasearch@5.52.1: + resolution: {integrity: sha512-fHA8+kXTbjagw3jkLiaS7KKrH8qe2DyOsiUhGlN4cdT77PEsfqXZl7ewDk1hsg+pJnPlnE50XtLxjR91iJOpmg==} engines: {node: '>= 14.0.0'} ansi-align@3.0.1: @@ -5849,6 +6027,10 @@ packages: asn1@0.2.6: resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} + asn1js@3.0.10: + resolution: {integrity: sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==} + engines: {node: '>=12.0.0'} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -5895,8 +6077,8 @@ packages: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} - b4a@1.8.0: - resolution: {integrity: sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==} + b4a@1.8.1: + resolution: {integrity: sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==} peerDependencies: react-native-b4a: '*' peerDependenciesMeta: @@ -5913,8 +6095,8 @@ packages: babel-plugin-dynamic-import-node@2.3.3: resolution: {integrity: sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==} - babel-plugin-polyfill-corejs2@0.4.14: - resolution: {integrity: sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==} + babel-plugin-polyfill-corejs2@0.4.17: + resolution: {integrity: sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 @@ -5923,8 +6105,13 @@ packages: peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - babel-plugin-polyfill-regenerator@0.6.5: - resolution: {integrity: sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==} + babel-plugin-polyfill-corejs3@0.14.2: + resolution: {integrity: sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-regenerator@0.6.8: + resolution: {integrity: sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 @@ -5941,8 +6128,8 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - bare-events@2.8.2: - resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==} + bare-events@2.8.3: + resolution: {integrity: sha512-HdUm8EMQBLaJvGUdidNNbqpA1kYkwNcb+MYxkxCLAPJGQzlv9J0C24h8V65Z4c5GLd/JEALDvpFCQgpLJqc0zw==} peerDependencies: bare-abort-controller: '*' peerDependenciesMeta: @@ -5958,15 +6145,15 @@ packages: bare-buffer: optional: true - bare-os@3.9.0: - resolution: {integrity: sha512-JTjuZyNIDpw+GytMO4a6TK1VXdVKKJr6DRxEHasyuYyShV2deuiHJK/ahGZlebc+SG0/wJCB9XK8gprBGDFi/Q==} + bare-os@3.9.1: + resolution: {integrity: sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==} engines: {bare: '>=1.14.0'} bare-path@3.0.0: resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} - bare-stream@2.13.0: - resolution: {integrity: sha512-3zAJRZMDFGjdn+RVnNpF9kuELw+0Fl3lpndM4NcEOhb9zwtSo/deETfuIwMSE5BXanA0FrN1qVjffGwAg2Y7EA==} + bare-stream@2.13.1: + resolution: {integrity: sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==} peerDependencies: bare-abort-controller: '*' bare-buffer: '*' @@ -5979,8 +6166,8 @@ packages: bare-events: optional: true - bare-url@2.4.2: - resolution: {integrity: sha512-/9a2j4ac6ckpmAHvod/ob7x439OAHst/drc2Clnq+reRYd/ovddwcF4LfoxHyNk5AuGBnPg+HqFjmE/Zpq6v0A==} + bare-url@2.4.3: + resolution: {integrity: sha512-Kccpc7ACfXaxfeInfqKcZtW4pT5YBn1mesc4sCsun6sRwtbJ4h+sNOaksUpYEJUKfN65YWC6Bw2OJEFiKxq8nQ==} base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -5989,8 +6176,8 @@ packages: resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} engines: {node: ^4.5.0 || >= 5.9} - baseline-browser-mapping@2.10.20: - resolution: {integrity: sha512-1AaXxEPfXT+GvTBJFuy4yXVHWJBXa4OdbIebGN/wX5DlsIkU0+wzGnd2lOzokSk51d5LUmqjgBLRLlypLUqInQ==} + baseline-browser-mapping@2.10.31: + resolution: {integrity: sha512-MujYO3eP72uvmSE0i4wltsodRfIpZATP3jvzRNRGGxgzId7aVocVJJV3nf01qnzzKFGxQVC9bpWxl5cjxTr/7Q==} engines: {node: '>=6.0.0'} hasBin: true @@ -6018,8 +6205,8 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - bits-ui@2.18.0: - resolution: {integrity: sha512-GLOBZRVy3hxNHIQ2MpD/+5aK9KcBFZRhUJtZ1UDABXdlVR4K6zFpgt4T+Rwuhf2sQzlc6yK1q/DprHPjwT4Pjw==} + bits-ui@2.18.1: + resolution: {integrity: sha512-KkemzKFH4T3gt3H+P86JcnAWExjByv/6vlwjm/BoCwTPHu03yiCdxbghdJLvFReQTe0acCAiRcKfmixxD6XvlA==} engines: {node: '>=20'} peerDependencies: '@internationalized/date': ^3.8.1 @@ -6028,8 +6215,8 @@ packages: bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - body-parser@1.20.4: - resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} + body-parser@1.20.5: + resolution: {integrity: sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} body-parser@2.2.2: @@ -6050,14 +6237,14 @@ packages: resolution: {integrity: sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==} engines: {node: '>=14.16'} - brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + brace-expansion@1.1.14: + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} brace-expansion@2.1.0: resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} - brace-expansion@5.0.5: - resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} engines: {node: 18 || 20 || >=22} braces@3.0.3: @@ -6089,12 +6276,13 @@ packages: resolution: {integrity: sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA==} engines: {node: '>=10.0.0'} - builtin-modules@5.0.0: - resolution: {integrity: sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==} + builtin-modules@5.2.0: + resolution: {integrity: sha512-02yxLeyxF4dNl6SlY6/5HfRSrSdZ/sCPoxy2kZNP5dZZX8LSAD9aE2gtJIUgWrsQTiMPl3mxESyrobSwvRGisQ==} engines: {node: '>=18.20'} - bullmq@5.76.1: - resolution: {integrity: sha512-9Xc5Pj4Ho0clodowuuUSydMOR4gCn+YxYYVQXbGJycO8r4jyxsff1rZl3CKj3k50c/B42gDDNTLJH6uwb3dYmg==} + bullmq@5.76.10: + resolution: {integrity: sha512-LWve7SpQjYSpCP2GEsWmoyzTz2H37L8HRmSTu3YihYsTOr5kJxrfEX6aEV7m6eskEMWXSHZYTMZepX6qNaH6CQ==} + engines: {node: '>=12.22.0'} bundle-name@4.1.0: resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} @@ -6125,14 +6313,14 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + bytestreamjs@2.0.1: + resolution: {integrity: sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==} + engines: {node: '>=6.0.0'} + cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} - cacache@20.0.3: - resolution: {integrity: sha512-3pUp4e8hv07k1QlijZu6Kn7c9+ZpWWk4j3F8N3xPuCExULobqJydKYOTj1FTq58srkJsXvO7LbGAH4C0ZU3WGw==} - engines: {node: ^20.17.0 || >=22.9.0} - cacheable-lookup@7.0.0: resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==} engines: {node: '>=14.16'} @@ -6145,8 +6333,8 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} - call-bind@1.0.8: - resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} engines: {node: '>= 0.4'} call-bound@1.0.4: @@ -6179,8 +6367,8 @@ packages: caniuse-api@3.0.0: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} - caniuse-lite@1.0.30001790: - resolution: {integrity: sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==} + caniuse-lite@1.0.30001793: + resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} canvas@3.2.3: resolution: {integrity: sha512-PzE5nJZPz72YUAfo8oTp0u3fqqY7IzlTubneAihqDYAUcBk7ryeCmBbdJBEdaH0bptSOe2VT2Zwcb3UaFyaSWw==} @@ -6243,14 +6431,6 @@ packages: resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==} engines: {node: '>= 6'} - chevrotain-allstar@0.3.1: - resolution: {integrity: sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==} - peerDependencies: - chevrotain: ^11.0.0 - - chevrotain@11.0.3: - resolution: {integrity: sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==} - chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -6288,9 +6468,6 @@ packages: cjs-module-lexer@2.2.0: resolution: {integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==} - class-transformer@0.5.1: - resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} - clean-css@5.3.3: resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==} engines: {node: '>= 10.0'} @@ -6446,6 +6623,10 @@ packages: resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} engines: {node: '>= 12'} + commander@9.5.0: + resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} + engines: {node: ^12.20.0 || >=14} + comment-json@5.0.0: resolution: {integrity: sha512-uiqLcOiVDJtBP8WGkZHEP+FZIhTzP1dxvn59EfoYUi9gqupjrBWVQkO2atDrbnKPwLeotFYDsuNb26uBMqB+hw==} engines: {node: '>= 6'} @@ -6479,9 +6660,6 @@ packages: resolution: {integrity: sha512-Uy5YN9KEu0WWDaZAVJ5FAmZoaJt9rdK6kH+utItPyGsCqCgaTKkrmZx3zoE0/3q6S3bcp3Ihkk+ZqPxWxFK5og==} engines: {node: '>=20'} - confbox@0.1.8: - resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - config-chain@1.1.13: resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} @@ -6512,10 +6690,18 @@ packages: resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} engines: {node: '>=18'} + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} + content-type@1.0.5: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} + content-type@2.0.0: + resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} + engines: {node: '>=18'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -6565,8 +6751,8 @@ packages: core-js-compat@3.49.0: resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} - core-js@3.47.0: - resolution: {integrity: sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==} + core-js@3.49.0: + resolution: {integrity: sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==} core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -6628,8 +6814,8 @@ packages: peerDependencies: postcss: ^8.4 - css-declaration-sorter@7.3.0: - resolution: {integrity: sha512-LQF6N/3vkAMYF4xoHLJfG718HRJh34Z8BnNhd6bosOMIVjMlhuZK5++oZa3uYAgrI5+7x2o27gUqTR2U/KjUOQ==} + css-declaration-sorter@7.4.0: + resolution: {integrity: sha512-LTuzjPoyA2vMGKKcaOqKSp7Ub2eGrNfKiZH4LpezxpNrsICGCSFvsQOI29psISxNZtaXibkC2CXzrQ5enMeGGw==} engines: {node: ^14 || ^16 || >=18} peerDependencies: postcss: ^8.0.9 @@ -6707,8 +6893,8 @@ packages: css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} - cssdb@8.5.2: - resolution: {integrity: sha512-Pmoj9RmD8RIoIzA2EQWO4D4RMeDts0tgAH0VXdlNdxjuBGI3a9wMOIcUwaPNmD4r2qtIa06gqkIf7sECl+cBCg==} + cssdb@8.9.0: + resolution: {integrity: sha512-J8jOU/hLjaXcO1LldOLraJSQpfLXRKof0I7mtbRyOy2AAXgqst0x9rlgi2qXeD6d0ou3ZLqcPAMqYVbpCbrxEw==} cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} @@ -6760,8 +6946,8 @@ packages: peerDependencies: cytoscape: ^3.2.0 - cytoscape@3.33.1: - resolution: {integrity: sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==} + cytoscape@3.33.4: + resolution: {integrity: sha512-HIN5Pmd9MrX9BkV7tDwnOcEJCSFvCpc8X97h3f508J6I5FsqAY65wKOCvgH2CuP42CaahWaz4tuh32SOOIH7ww==} engines: {node: '>=0.10'} d3-array@2.12.1: @@ -6820,8 +7006,8 @@ packages: resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} engines: {node: '>=12'} - d3-format@3.1.0: - resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} engines: {node: '>=12'} d3-geo@3.1.1: @@ -6907,15 +7093,15 @@ packages: resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==} engines: {node: '>=0.12'} - dagre-d3-es@7.0.13: - resolution: {integrity: sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==} + dagre-d3-es@7.0.14: + resolution: {integrity: sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==} data-urls@5.0.0: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} - dayjs@1.11.19: - resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} + dayjs@1.11.20: + resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==} debounce-fn@6.0.0: resolution: {integrity: sha512-rBMW+F2TXryBwB54Q0d8drNEI+TfoS9JpNTAoVpukbWEhjXQq4rySFYLaqXMFXwdv61Zb2OHtj5bviSoimqxRQ==} @@ -6961,8 +7147,8 @@ packages: decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} - decode-named-character-reference@1.2.0: - resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} @@ -6990,8 +7176,8 @@ packages: resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} engines: {node: '>=18'} - default-browser@5.4.0: - resolution: {integrity: sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==} + default-browser@5.5.0: + resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} engines: {node: '>=18'} defaults@1.0.4: @@ -7017,8 +7203,8 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} - delaunator@5.0.1: - resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==} + delaunator@5.1.0: + resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==} delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} @@ -7062,8 +7248,8 @@ packages: engines: {node: '>= 4.0.0'} hasBin: true - devalue@5.7.1: - resolution: {integrity: sha512-MUbZ586EgQqdRnC4yDrlod3BEdyvE4TapGYHMW2CiaW+KkkFmWEFqBUaLltEZCGi0iFXCEjRF0OjF0DV2QHjOA==} + devalue@5.8.1: + resolution: {integrity: sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==} devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} @@ -7110,8 +7296,8 @@ packages: resolution: {integrity: sha512-XJgGhoR/CLpqshm4d3L7rzH6t8NgDFUIIpztYlLHIApeJjMZKYJMz2zxPsYxnejq5h3ELYSw/RBsi3t5h7gNTA==} engines: {node: '>= 8.0'} - dockerode@4.0.10: - resolution: {integrity: sha512-8L/P9JynLBiG7/coiA4FlQXegHltRqS0a+KqI44P1zgQh8QLHTg7FKOwhkBgSJwZTeHsq30WRoVFLuwkfK0YFg==} + dockerode@4.0.12: + resolution: {integrity: sha512-/bCZd6KlGcjZO8Buqmi/vXuqEGVEZ0PNjx/biBNqJD3MhK9DmdiAuKxqfNhflgDESDIiBz3qF+0e55+CpnrUcw==} engines: {node: '>= 8.0'} docusaurus-lunr-search@3.6.0: @@ -7151,8 +7337,8 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} - dompurify@3.3.1: - resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==} + dompurify@3.4.5: + resolution: {integrity: sha512-OrwIBKsdNSVEeubdJ1HBv/wNENRM9ytAVCv7YXt//A3vPdVMNuACRqK9mXCGCBW2ln7BT/A4X0jXHo2Gu89miA==} domutils@2.8.0: resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} @@ -7194,8 +7380,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.343: - resolution: {integrity: sha512-YHnQ3MXI08icvL9ZKnEBy05F2EQ8ob01UaMOuMbM8l+4UcAq6MPPbBTJBbsBUg3H8JeZNt+O4fjsoWth3p6IFg==} + electron-to-chromium@1.5.360: + resolution: {integrity: sha512-GkcBt6YYAw9SxFWn+xVar4cLVGlXVuswwtRLBozi2zp0GjXs4ZnOrqV4zbXzg35n7w81hCkyJNYicgXlVHAmBA==} emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} @@ -7220,25 +7406,22 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} - encoding@0.1.13: - resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} - end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - engine.io-client@6.6.4: - resolution: {integrity: sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==} + engine.io-client@6.6.5: + resolution: {integrity: sha512-QCwxUDULPlXv8F6tqMMKx5dNkTe6OaBYRMPYeXKBlyOoKvAmE0ac6pW7fFhSscJ/5SI7666/U/B+MElbsrJlIg==} engine.io-parser@5.2.3: resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} engines: {node: '>=10.0.0'} - engine.io@6.6.5: - resolution: {integrity: sha512-2RZdgEbXmp5+dVbRm0P7HQUImZpICccJy7rN7Tv+SFa55pH+lxnuw6/K1ZxxBfHoYpSkHLAO92oa8O4SwFXA2A==} + engine.io@6.6.8: + resolution: {integrity: sha512-2agL3ueZhqxoVrfmntO8yuVj+uNSlIOnhykYHk3Cq0ShYPdUjjUiSJrQvXjq01I9jAuI0Zl2YO8Evv5Mqytm5g==} engines: {node: '>=10.2.0'} - enhanced-resolve@5.21.0: - resolution: {integrity: sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==} + enhanced-resolve@5.21.5: + resolution: {integrity: sha512-mLCNbrQli11K1ySUmuNt4ZUB3OpGIDq4q2vTBTf5cL2lpsRjI9QKqSD0ndjW8FyvcW/Jj46gMe9syyHAsvMa/A==} engines: {node: '>=10.13.0'} entities@2.2.0: @@ -7264,9 +7447,6 @@ packages: resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - err-code@2.0.3: - resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} - error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} @@ -7292,6 +7472,9 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} + es-toolkit@1.46.1: + resolution: {integrity: sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==} + es5-ext@0.10.64: resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==} engines: {node: '>=0.10'} @@ -7322,6 +7505,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + esbuild@0.28.0: resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==} engines: {node: '>=18'} @@ -7369,9 +7557,9 @@ packages: oxlint: optional: true - eslint-plugin-compat@7.0.1: - resolution: {integrity: sha512-wDID2fVIAfxV9R1uSkCn5HscnNu8yMxDF1IaQGyD1C6XuWwJbuaDgMOSkVgOom0LzY8z0fXXXCy7AQQTERQUvQ==} - engines: {node: '>=18.x'} + eslint-plugin-compat@7.0.2: + resolution: {integrity: sha512-gN8hF+4NzMsHUbr4m/TYZK0FtW3DcV4g8rXpTsY2EV5xiRD8jsilUlB9lNSkGGX0veDCxMhKSWbSd+faJByQDA==} + engines: {node: '>=22.x'} peerDependencies: eslint: ^9.0.0 || ^10.0.0 @@ -7429,8 +7617,8 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.2.1: - resolution: {integrity: sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q==} + eslint@10.4.0: + resolution: {integrity: sha512-loXy6bWOoP3EP6JA7jo6p5jMpBJmHmsNZM5SFRHLdh1MGOPurMnNBj4ZlAbaqUAaQWbCr7jHV4P7gzAyryZWkQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: @@ -7463,8 +7651,13 @@ packages: resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} engines: {node: '>=0.10'} - esrap@2.2.4: - resolution: {integrity: sha512-suICpxAmZ9A8bzJjEl/+rLJiDKC0X4gYWUxT6URAWBLvlXmtbZd5ySMu/N2ZGEtMCAmflUDPSehrP9BQcsGcSg==} + esrap@2.2.9: + resolution: {integrity: sha512-4KijP+NxCWthMCUC3qHbE6n4vCjqgJS1uAYKhuT/GWfFTf1Qyive2TgOjep+gzbSzRfnNyaN/UU9YmdOt8Eg0A==} + peerDependencies: + '@typescript-eslint/types': ^8.2.0 + peerDependenciesMeta: + '@typescript-eslint/types': + optional: true esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} @@ -7513,8 +7706,8 @@ packages: resolution: {integrity: sha512-UVQ72Rqjy/ZKQalzV5dCCJP80GrmPrMxh6NlNf+erV6ObL0ZFkhCstWRawS85z3smdr3d2wXPsZEY7rDPfGd2g==} engines: {node: '>=6.0.0'} - eta@4.5.1: - resolution: {integrity: sha512-EaNCGm+8XEIU7YNcc+THptWAO5NfKBHHARxt+wxZljj9bTr/+arRoOm9/MpGt4n6xn9fLnPFRSoLD0WFYGFUxQ==} + eta@4.6.0: + resolution: {integrity: sha512-lW6is4T1NFOYnmqGZIfvixqj7A7sSvScF+DN8EK6K58xI5MZ5UvYe0GjopxOXQtZvUn4eDdVuZ8XSoYWTMEKwA==} engines: {node: '>=20'} etag@1.8.1: @@ -7542,10 +7735,6 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} - eventsource-parser@3.0.6: - resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} - engines: {node: '>=18.0.0'} - execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -7574,8 +7763,8 @@ packages: exponential-backoff@3.1.3: resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} - express@4.22.1: - resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==} + express@4.22.2: + resolution: {integrity: sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==} engines: {node: '>= 0.10.0'} express@5.2.1: @@ -7592,8 +7781,8 @@ packages: extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - fabric@7.3.1: - resolution: {integrity: sha512-RoLAQzUX+/3RNMYKliuN0P2HXdSDEGzyjS7FnmEbo3nhb8LFh59T+l3f6ApIu5LT4YB49YfMNrEajeIbutmD7Q==} + fabric@7.4.0: + resolution: {integrity: sha512-NalYDc3eifTl1C33zryQwpH6+XA/2ClxQrH9vkASkZw3tbkRmorpikhYMmxhUTmi7O3e9ODz0vOT8qfaCh9IVA==} engines: {node: '>=20.0.0'} factory.ts@1.4.2: @@ -7622,8 +7811,8 @@ packages: fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - fast-uri@3.1.0: - resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fast-uri@3.1.2: + resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} @@ -7648,8 +7837,8 @@ packages: resolution: {integrity: sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==} engines: {node: '>=0.4.0'} - fflate@0.8.2: - resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} @@ -7720,8 +7909,8 @@ packages: engines: {node: '>=18'} deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - follow-redirects@1.15.11: - resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} engines: {node: '>=4.0'} peerDependencies: debug: '*' @@ -7781,18 +7970,14 @@ packages: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} - fs-extra@11.3.4: - resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==} + fs-extra@11.3.5: + resolution: {integrity: sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==} engines: {node: '>=14.14'} fs-minipass@2.1.0: resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} engines: {node: '>= 8'} - fs-minipass@3.0.3: - resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - fs-monkey@1.1.0: resolution: {integrity: sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==} @@ -7817,6 +8002,10 @@ packages: engines: {node: '>=10'} deprecated: This package is no longer supported. + generic-pool@3.9.0: + resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==} + engines: {node: '>= 4'} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -7824,8 +8013,8 @@ packages: geo-coordinates-parser@1.7.4: resolution: {integrity: sha512-gVGxBW+s1csexXVMf5bIwz3TH9n4sCEglOOOqmrPk8YazUI5f79jCowKjTw05m/0h1//3+Z2m/nv8IIozgZyUw==} - geo-tz@8.1.6: - resolution: {integrity: sha512-6YEper1rtHi1l3ZS99oy9ZBvrBO4qsLzvQwZoxYtV3fyxPuxh7yqiOUWRVs1bzSj693EXEO4k60jKXQmkY/JnQ==} + geo-tz@8.1.7: + resolution: {integrity: sha512-skkwQunTD1hpixwESUv8GeLLl8DElpUmnZfp9trQVgVGVIRTeQJHmCcWELCr8lsiVKdu661TWzR9hsdlGCEEKA==} engines: {node: '>=16'} geobuf@3.0.2: @@ -7840,8 +8029,8 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-east-asian-width@1.5.0: - resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} + get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} engines: {node: '>=18'} get-intrinsic@1.3.0: @@ -7863,8 +8052,8 @@ packages: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} - get-tsconfig@4.13.0: - resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} @@ -7921,8 +8110,8 @@ packages: resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} engines: {node: '>=18'} - globals@17.5.0: - resolution: {integrity: sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==} + globals@17.6.0: + resolution: {integrity: sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==} engines: {node: '>=18'} globalyzer@0.1.0: @@ -8001,8 +8190,8 @@ packages: resolution: {integrity: sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + hasown@2.0.3: + resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} engines: {node: '>= 0.4'} hast-util-from-parse5@6.0.1: @@ -8109,8 +8298,8 @@ packages: html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} - html-webpack-plugin@5.6.5: - resolution: {integrity: sha512-4xynFbKNNk+WlzXeQQ+6YYsH2g7mpfPszQZUi3ovKlj+pDmngQ7vRXjrrmGROabmKwyQkcgcX5hqfOwHbFmK5g==} + html-webpack-plugin@5.6.7: + resolution: {integrity: sha512-md+vXtdCAe60s1k6AU3dUyMJnDxUyQAwfwPKoLisvgUF1IXjtlLsk2se54+qfL9Mdm26bbwvjJybpNx48NKRLw==} engines: {node: '>=10.13.0'} peerDependencies: '@rspack/core': 0.x || 1.x @@ -8137,10 +8326,6 @@ packages: http-deceiver@1.2.7: resolution: {integrity: sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==} - http-errors@1.6.3: - resolution: {integrity: sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==} - engines: {node: '>= 0.6'} - http-errors@1.8.1: resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==} engines: {node: '>= 0.6'} @@ -8244,14 +8429,17 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} - import-in-the-middle@3.0.0: - resolution: {integrity: sha512-OnGy+eYT7wVejH2XWgLRgbmzujhhVIATQH0ztIeRilwHBjTeG3pD+XnH3PKX0r9gJ0BuJmJ68q/oh9qgXnNDQg==} + import-in-the-middle@3.0.1: + resolution: {integrity: sha512-pYkiyXVL2Mf3pozdlDGV6NAObxQx13Ae8knZk1UJRJ6uRW/ZRmTGHlQYtrsSl7ubuE5F8CD1z+s1n4RHNuTtuA==} engines: {node: '>=18'} import-lazy@4.0.0: resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} engines: {node: '>=8'} + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -8272,9 +8460,6 @@ packages: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - inherits@2.0.3: - resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} - inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -8302,8 +8487,8 @@ packages: intl-messageformat@10.7.18: resolution: {integrity: sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==} - intl-messageformat@11.2.1: - resolution: {integrity: sha512-1gAVEUt3wEPvTqML4Fsw9klZV5j0vszQxayP/fi6gUroAc8AUHiNaisBKLWxybL1AdWq1mP07YV1q8v4N92ilQ==} + intl-messageformat@11.2.6: + resolution: {integrity: sha512-afAN2yNN7zjB77G1ZC5L8GtLrEshyBvOQXz88flxCO/ocTIQist98gu0r/O6H/SSiQhQsOOtWPxmCEvtDABXXQ==} invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} @@ -8312,16 +8497,12 @@ packages: resolution: {integrity: sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==} engines: {node: '>=12.22.0'} - ip-address@10.1.0: - resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} - engines: {node: '>= 12'} - ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} - ipaddr.js@2.3.0: - resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==} + ipaddr.js@2.4.0: + resolution: {integrity: sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==} engines: {node: '>= 10'} is-alphabetical@2.0.1: @@ -8349,8 +8530,8 @@ packages: resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} hasBin: true - is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} engines: {node: '>= 0.4'} is-decimal@2.0.1: @@ -8406,8 +8587,8 @@ packages: resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} engines: {node: '>=12'} - is-network-error@1.3.0: - resolution: {integrity: sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==} + is-network-error@1.3.2: + resolution: {integrity: sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==} engines: {node: '>=16'} is-npm@6.1.0: @@ -8559,8 +8740,8 @@ packages: resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} hasBin: true - jiti@2.6.1: - resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true jmespath@0.16.0: @@ -8570,8 +8751,8 @@ packages: joi@17.13.3: resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} - jose@6.2.2: - resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==} + jose@6.2.3: + resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} js-tokens@10.0.0: resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} @@ -8623,9 +8804,6 @@ packages: json-schema-typed@8.0.2: resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} - json-schema@0.4.0: - resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} - json-source-map@0.6.1: resolution: {integrity: sha512-1QoztHPsMQqhDq0hlXY5ZqcEdUzxQEIxgFkKl4WUp2pgShObl+9ovi4kRh2TfvAfxAoHOJ9vIMEqk3k4iex7tg==} @@ -8671,12 +8849,12 @@ packages: jws@4.0.1: resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} - katex@0.16.27: - resolution: {integrity: sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw==} + katex@0.16.47: + resolution: {integrity: sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==} hasBin: true - kdbush@4.0.2: - resolution: {integrity: sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==} + kdbush@4.1.0: + resolution: {integrity: sha512-e9vurzrXJQrFX6ckpHP3bvj5l+9CnYzkxDNnNQ1h2QTqdWsUAJgXiKdGNcOa1EY85dU8KbQ+z/FdQdB7P+9yfQ==} keygrip@1.1.0: resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} @@ -8724,16 +8902,12 @@ packages: resolution: {integrity: sha512-nbD8lB9EB3wNdMhOCdx5Li8DxnLbvKByylRLcJ1h+4SkrowVeECAyZlyiKMThF7xFdRz0jSQ2MoJr+wXux2y0Q==} engines: {node: '>=20.0.0'} - langium@3.3.1: - resolution: {integrity: sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==} - engines: {node: '>=16.0.0'} - latest-version@7.0.0: resolution: {integrity: sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==} engines: {node: '>=14.16'} - launch-editor@2.12.0: - resolution: {integrity: sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==} + launch-editor@2.13.2: + resolution: {integrity: sha512-4VVDnbOpLXy/s8rdRCSXb+zfMeFR0WlJWpET1iA9CQdlZDfwyLjUuGQzXU4VeOoey6AicSAluWan7Etga6Kcmg==} layout-base@1.0.2: resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} @@ -8849,8 +9023,8 @@ packages: resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - loader-runner@4.3.1: - resolution: {integrity: sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==} + loader-runner@4.3.2: + resolution: {integrity: sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==} engines: {node: '>=6.11.5'} loader-utils@2.0.4: @@ -8872,9 +9046,6 @@ packages: resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - lodash-es@4.17.21: - resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} - lodash-es@4.18.1: resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} @@ -8955,8 +9126,8 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.3.5: - resolution: {integrity: sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==} + lru-cache@11.5.0: + resolution: {integrity: sha512-5YgH9UJd7wVb9hIouI2adWpgqrrICkt070Dnj8EUY1+B4B2P9eRLPAkAAo6NICA7CEhOIeBHl46u9zSNpNu7zA==} engines: {node: 20 || >=22} lru-cache@5.1.1: @@ -8965,8 +9136,8 @@ packages: lru-queue@0.1.0: resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==} - lunr-languages@1.14.0: - resolution: {integrity: sha512-hWUAb2KqM3L7J5bcrngszzISY4BxrXn/Xhbb9TTCJYEGqlR1nG67/M14sp09+PTIRklobrn57IAxcdcO/ZFyNA==} + lunr-languages@1.20.0: + resolution: {integrity: sha512-3LVgE7ekWXt04NBci/hjm+NXJxXZeRXuyClL0kA0HONyBOjxhP3ZQkuWIM4Ok3pbeptUW/rj3XcJcJuJVPwPYA==} lunr@2.3.9: resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} @@ -8988,8 +9159,8 @@ packages: magicast@0.3.5: resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} - magicast@0.5.2: - resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==} + magicast@0.5.3: + resolution: {integrity: sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==} make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} @@ -8999,10 +9170,6 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} - make-fetch-happen@15.0.3: - resolution: {integrity: sha512-iyyEpDty1mwW3dGlYXAJqC/azFn5PPvgKVwXayOGBSmKLxhKZ9fg4qIan2ePpp1vJIwfFiO34LAPZgq9SZW9Aw==} - engines: {node: ^20.17.0 || >=22.9.0} - maplibre-gl@5.24.0: resolution: {integrity: sha512-ALyFxgtd5R+65UqZ/++lOqwWcC0SNho9c27fYSyLmG7AfnAul2o46F05aDJGPbFU57wos9dgcIySHs0Xe6ia3A==} engines: {node: '>=16.14.0', npm: '>=8.1.0'} @@ -9037,8 +9204,8 @@ packages: mdast-util-find-and-replace@3.0.2: resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} - mdast-util-from-markdown@2.0.2: - resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + mdast-util-from-markdown@2.0.3: + resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} mdast-util-frontmatter@2.0.1: resolution: {integrity: sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==} @@ -9091,8 +9258,8 @@ packages: mdn-data@2.0.30: resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} - mdn-data@2.27.1: - resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + mdn-data@2.28.0: + resolution: {integrity: sha512-uy9AS1yt+wW5eUEefgE3lOpqPghanUttycV0GXKbiXyBjwvbeE8XPj4u1C+voRfz7dEjwU4NDHTMfZ/s/JtZrQ==} media-chrome@4.19.0: resolution: {integrity: sha512-HWhDTwts+BSbdPkkB1VsJXp5kvL0IxY7xFT5tBwliM2+89kTPVTnHnev+9it2f9PweANjT/C8/C/S0PW9oyZbA==} @@ -9135,8 +9302,8 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - mermaid@11.12.2: - resolution: {integrity: sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==} + mermaid@11.15.0: + resolution: {integrity: sha512-pTMbcf3rWdtLiYGpmoTjHEpeY8seiy6sR+9nD7LOs8KfUbHE4lOUAprTRqRAcWSQ6MQpdX+YEsxShtGsINtPtw==} methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} @@ -9323,8 +9490,8 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - mini-css-extract-plugin@2.9.4: - resolution: {integrity: sha512-ZWYT7ln73Hptxqxk2DxPU9MmapXRhxkJD6tkSR04dnQxm8BGu2hzgKLugK5yySD97u/8yy7Ma7E76k9ZdvtjkQ==} + mini-css-extract-plugin@2.10.2: + resolution: {integrity: sha512-AOSS0IdEB95ayVkxn5oGzNQwqAi2J0Jb/kKm43t7H73s8+f5873g0yuj0PNvK4dO75mu5DHg4nlgp4k6Kga8eg==} engines: {node: '>= 12.13.0'} peerDependencies: webpack: ^5.0.0 @@ -9350,26 +9517,6 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - minipass-collect@2.0.1: - resolution: {integrity: sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==} - engines: {node: '>=16 || 14 >=14.17'} - - minipass-fetch@5.0.1: - resolution: {integrity: sha512-yHK8pb0iCGat0lDrs/D6RZmCdaBT64tULXjdxjSMAqoDi18Q3qKEUTHypHQZQd9+FYpIS+lkvpq6C/R6SbUeRw==} - engines: {node: ^20.17.0 || >=22.9.0} - - minipass-flush@1.0.5: - resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} - engines: {node: '>= 8'} - - minipass-pipeline@1.2.4: - resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} - engines: {node: '>=8'} - - minipass-sized@2.0.0: - resolution: {integrity: sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==} - engines: {node: '>=8'} - minipass@3.3.6: resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} engines: {node: '>=8'} @@ -9407,11 +9554,8 @@ packages: engines: {node: '>=10'} hasBin: true - mlly@1.8.0: - resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} - - mnemonist@0.40.3: - resolution: {integrity: sha512-Vjyr90sJ23CKKH/qPAgUKicw/v6pRoamxIEDFOF8uSgFME7DqPRpHgRTejWVjkdGg5dXj0/NyxZHZ9bcjH+2uQ==} + mnemonist@0.40.4: + resolution: {integrity: sha512-ZAv+KNavneRVzu4tUeOgzkScI3W5BGwZ3rkxIpKtzzVgfTtWQFN1CgX0U72cyvyh3iTuHL3SiSmrQxTlryEIcw==} mock-fs@5.5.0: resolution: {integrity: sha512-d/P1M/RacgM3dB0sJ8rjeRNXxtapkPCUnMGmIN0ixJ16F/E4GUZCvWcSGfWGz8eaXYvn1s9baUwNjI4LOPEjiA==} @@ -9441,8 +9585,8 @@ packages: resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==} hasBin: true - msgpackr@1.11.5: - resolution: {integrity: sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==} + msgpackr@2.0.1: + resolution: {integrity: sha512-9J+tqTEsbHqY8YohazYgty7LgerFIWxvMLpUjqETSmjHojtJm2WnX2kK/2a1fLI7CO7ERP1YSEUXMucz4j+yBA==} multer@2.1.1: resolution: {integrity: sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==} @@ -9462,19 +9606,23 @@ packages: resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} engines: {node: ^18.17.0 || >=20.5.0} + mylas@2.1.14: + resolution: {integrity: sha512-BzQguy9W9NJgoVn2mRWzbFrFWWztGCcng2QI9+41frfk+Athwgx3qhqhvStz7ExeUUu7Kzw427sNzHpEZNINog==} + engines: {node: '>=16.0.0'} + mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nan@2.26.2: - resolution: {integrity: sha512-0tTvBTYkt3tdGw22nrAy50x7gpbGCCFH3AFcyS5WiUu7Eu4vWlri1woE6qHBSfy11vksDqkiwjOnlR7WV8G1Hw==} + nan@2.27.0: + resolution: {integrity: sha512-hC+0LidcL3XE4rp1C4H54KujgXKzbfyTngZTwBByQxsOxCEKZT0MPQ4hOKUH2jU1OYstqdDH4onyHPDzcV0XdQ==} - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanoid@5.1.9: - resolution: {integrity: sha512-ZUvP7KeBLe3OZ1ypw6dI/TzYJuvHP77IM4Ry73waSQTLn8/g8rpdjfyVAh7t1/+FjBtG4lCP42MEbDxOsRpBMw==} + nanoid@5.1.11: + resolution: {integrity: sha512-v+KEsUv2ps74PaSKv0gHTxTCgMXOIfBEbaqa6w6ISIGC7ZsvHN4N9oJ8d4cmf0n5oTzQz2SLmThbQWhjd/8eKg==} engines: {node: ^18 || >=20} hasBin: true @@ -9530,15 +9678,15 @@ packages: kysely: 0.x reflect-metadata: ^0.1.13 || ^0.2.2 - nestjs-otel@8.0.2: - resolution: {integrity: sha512-IQZ4MRb54WqRPooFPWrbqdOt+RckwaBjPoQy+axm9Jtri0DlOM6B3mSfzKHAJOwjj6YFaq10DXLOcYrqI8IsXA==} + nestjs-otel@8.0.3: + resolution: {integrity: sha512-48W0ksSnvRZSO7GUY3R76TeHtPwThAhjPOeyH7EOzhLnsrPQ7Msk8nPrU5FpTiIzUapoubdx+jfolRlJC8L21g==} engines: {node: '>= 22'} peerDependencies: '@nestjs/common': '>= 11 < 12' '@nestjs/core': '>= 11 < 12' - nestjs-zod@5.3.0: - resolution: {integrity: sha512-QY6imXm9heMOpWigjFHgMWPvc1ZQHeNQ7pdogo9Q5xj5F8HpqZ972vKlVdkaTyzYlOXJP/yVy3wlF1EjubDQPg==} + nestjs-zod@5.4.0: + resolution: {integrity: sha512-dxVpy1fjfK4kp+ztK+7xQP46fpvZxkeR/jcEdIvEGh/2o71iwXuy/hrKOWSPhJ1nQXV4iBdHqMizndn2GTaXDg==} peerDependencies: '@nestjs/common': ^10.0.0 || ^11.0.0 '@nestjs/swagger': ^7.4.2 || ^8.0.0 || ^11.0.0 @@ -9567,8 +9715,8 @@ packages: node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} - node-addon-api@8.5.0: - resolution: {integrity: sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==} + node-addon-api@8.7.0: + resolution: {integrity: sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==} engines: {node: ^18 || ^20 || >= 21} node-emoji@1.11.0: @@ -9587,10 +9735,6 @@ packages: encoding: optional: true - node-forge@1.3.3: - resolution: {integrity: sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==} - engines: {node: '>= 6.13.0'} - node-gyp-build-optional-packages@5.2.2: resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} hasBin: true @@ -9599,16 +9743,16 @@ packages: resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} hasBin: true - node-gyp@12.2.0: - resolution: {integrity: sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ==} + node-gyp@12.3.0: + resolution: {integrity: sha512-QNcUWM+HgJplcPzBvFBZ9VXacyGZ4+VTOb80PwWR+TlVzoHbRKULNEzpRsnaoxG3Wzr7Qh7BYxGDU3CbKib2Yg==} engines: {node: ^20.17.0 || >=22.9.0} hasBin: true - node-releases@2.0.38: - resolution: {integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==} + node-releases@2.0.44: + resolution: {integrity: sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==} - nodemailer@8.0.5: - resolution: {integrity: sha512-0PF8Yb1yZuQfQbq+5/pZJrtF6WQcjTd5/S4JOHs9PGFxuTqoB/icwuB44pOdURHJbRKX1PPoJZtY7R4VUoCC8w==} + nodemailer@8.0.7: + resolution: {integrity: sha512-pkjE4mkBzQjdJT4/UmlKl3pX0rC9fZmjh7c6C9o7lv66Ac6w9WCnzPzhbPNxwZAzlF4mdq4CSWB5+FbK6FWCow==} engines: {node: '>=6.0.0'} nopt@1.0.10: @@ -9629,8 +9773,8 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - normalize-url@8.1.0: - resolution: {integrity: sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==} + normalize-url@8.1.1: + resolution: {integrity: sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==} engines: {node: '>=14.16'} not@0.1.0: @@ -9667,8 +9811,8 @@ packages: engines: {node: '>=18'} hasBin: true - oauth4webapi@3.8.5: - resolution: {integrity: sha512-A8jmyUckVhRJj5lspguklcl90Ydqk61H3dcU0oLhH3Yv13KpAliKTt5hknpGGPZSSfOwGyraNEFmofDYH+1kSg==} + oauth4webapi@3.8.6: + resolution: {integrity: sha512-iwemM91xz8nryHti2yTmg5fhyEMVOkOXwHNqbvcATjyajb5oQxCQzrNOA6uElRHuMhQQTKUyFKV9y/CNyg25BQ==} object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} @@ -9699,8 +9843,8 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} - oidc-provider@9.8.2: - resolution: {integrity: sha512-Iu/VahRoAhgmzKdvqSX/4ZzrG11Zf6NHuhu1wLkoblBnMUIwud++D2lftK8jV/gLhRl3Fppa3RINYCf/675cjw==} + oidc-provider@9.8.3: + resolution: {integrity: sha512-YkchaAyVAZbsn/l7IQhcEMdeDL3lwSo/PNUtnsXSqPqT7EG8DRko0EAWzHd/n9VfCtKVkxGjYOY4h4UwFcWnUA==} on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} @@ -9737,8 +9881,8 @@ packages: resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} hasBin: true - openid-client@6.8.3: - resolution: {integrity: sha512-AoY/NaN9esS3+xvHInFSK0g3skSfeE0uqQAKRj4rB6/GsBIvzwTUaYo9+HcqpKIaP0dP85p5W07hayKgS4GAeA==} + openid-client@6.8.4: + resolution: {integrity: sha512-QSw0BA08piujetEwfZsHoTrDpMEha7GDZDicQqVwX4u0ChCjefvjDB++TZ8BTg76UpwhzIQgdvvfgfl3HpCSAw==} optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} @@ -9788,10 +9932,6 @@ packages: resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} engines: {node: '>=10'} - p-map@7.0.4: - resolution: {integrity: sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==} - engines: {node: '>=18'} - p-queue@6.6.2: resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} engines: {node: '>=8'} @@ -9930,30 +10070,30 @@ packages: peberminta@0.9.0: resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} - pg-cloudflare@1.3.0: - resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==} + pg-cloudflare@1.4.0: + resolution: {integrity: sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==} - pg-connection-string@2.12.0: - resolution: {integrity: sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==} + pg-connection-string@2.13.0: + resolution: {integrity: sha512-EMnU9E2fSULdsbErBbMaXJvFeD9B4+nPcM3f+4lsiCR0BHLPrLVjv3DbyM2hgQQviKJaTWIRRTjKjWlHg3p2ig==} pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} - pg-pool@3.13.0: - resolution: {integrity: sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==} + pg-pool@3.14.0: + resolution: {integrity: sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==} peerDependencies: pg: '>=8.0' - pg-protocol@1.13.0: - resolution: {integrity: sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==} + pg-protocol@1.14.0: + resolution: {integrity: sha512-n5taZ1kO3s9ngDTVxsEznOqCyToTgz0FLuPq0B33COy5pPpuWJpY3/2oRBVETuOgzdqRXfWpM9HIhp2LBBT1BA==} pg-types@2.2.0: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} engines: {node: '>=4'} - pg@8.20.0: - resolution: {integrity: sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==} + pg@8.21.0: + resolution: {integrity: sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==} engines: {node: '>= 16.0.0'} peerDependencies: pg-native: '>=3.0.1' @@ -9987,19 +10127,24 @@ packages: resolution: {integrity: sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==} engines: {node: '>=14.16'} - pkg-types@1.3.1: - resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + pkijs@3.4.0: + resolution: {integrity: sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==} + engines: {node: '>=16.0.0'} - playwright-core@1.59.1: - resolution: {integrity: sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==} + playwright-core@1.60.0: + resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==} engines: {node: '>=18'} hasBin: true - playwright@1.59.1: - resolution: {integrity: sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==} + playwright@1.60.0: + resolution: {integrity: sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==} engines: {node: '>=18'} hasBin: true + plimit-lit@1.6.1: + resolution: {integrity: sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==} + engines: {node: '>=12'} + pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} @@ -10386,8 +10531,8 @@ packages: peerDependencies: postcss: ^8.4 - postcss-preset-env@10.5.0: - resolution: {integrity: sha512-xgxFQPAPxeWmsgy8cR7GM1PGAL/smA5E9qU7K//D4vucS01es3M0fDujhDJn3kY8Ip7/vVYcecbe1yY+vBo3qQ==} + postcss-preset-env@10.6.1: + resolution: {integrity: sha512-yrk74d9EvY+W7+lO9Aj1QmjWY9q5NsKjK2V9drkOPZB/X6KZ0B3igKsHUYakb7oYVhnioWypQX3xGuePf89f3g==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -10474,8 +10619,8 @@ packages: peerDependencies: postcss: ^8.4.31 - postcss@8.5.12: - resolution: {integrity: sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==} + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} engines: {node: ^10 || ^12 || >=14} postgres-array@2.0.0: @@ -10535,8 +10680,8 @@ packages: peerDependencies: prettier: ^3.0.0 - prettier-plugin-svelte@3.5.1: - resolution: {integrity: sha512-65+fr5+cgIKWKiqM1Doum4uX6bY8iFCdztvvp2RcF+AJoieaw9kJOFMNcJo/bkmKYsxFaM9OsVZK/gWauG/5mg==} + prettier-plugin-svelte@3.5.2: + resolution: {integrity: sha512-ItFouLvzSFE3ulNl4DKoWM3BGcbDCNVpIyy/Y3F2gC3aNiGLxtFUdffVqO5Z5hhYG+DFT5KULWaxmeFFpdbvaQ==} peerDependencies: prettier: ^3.0.0 svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 @@ -10577,10 +10722,6 @@ packages: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} - promise-retry@2.0.1: - resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} - engines: {node: '>=10'} - prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -10604,12 +10745,8 @@ packages: proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} - protobufjs@7.5.5: - resolution: {integrity: sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg==} - engines: {node: '>=12.0.0'} - - protobufjs@8.0.1: - resolution: {integrity: sha512-NWWCCscLjs+cOKF/s/XVNFRW7Yih0fdH+9brffR5NZCy8k42yRdl5KlWKMVXuI1vfCoy4o1z80XR/W/QUb3V3w==} + protobufjs@7.6.0: + resolution: {integrity: sha512-LtESOsMPTZgyYtwxhvdgdjGL0HmXEaRA/hVD6sol4zA60hVXXXP/SGmxnqDbgGE8gy7pYex7cym+5vYPcmaXBQ==} engines: {node: '>=12.0.0'} protocol-buffers-schema@3.6.1: @@ -10633,15 +10770,26 @@ packages: resolution: {integrity: sha512-LjgDO2zPtoXP2wJpDjZrGdojii1uqO0cnwKoIoUzkfS98HDmbeiGmYiXo3lXeFlq2xvne1QFQhwYXSUCLKtEuA==} engines: {node: '>=12.20'} + pvtsutils@1.3.6: + resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==} + + pvutils@1.1.5: + resolution: {integrity: sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==} + engines: {node: '>=16.0.0'} + qrcode@1.5.4: resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} engines: {node: '>=10.13.0'} hasBin: true - qs@6.14.1: - resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==} + qs@6.15.2: + resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==} engines: {node: '>=0.6'} + queue-lit@1.5.2: + resolution: {integrity: sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==} + engines: {node: '>=12'} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -10692,10 +10840,10 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true - react-dom@19.2.5: - resolution: {integrity: sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==} + react-dom@19.2.6: + resolution: {integrity: sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==} peerDependencies: - react: ^19.2.5 + react: ^19.2.6 react-email@5.2.11: resolution: {integrity: sha512-9TzTGRGeavli/iv1RICVOePnFOeG2YRyr8kAyXj6Zgudteq60uA0txDwe4q1zIjQ+08fcbzBkSnI9HgPkSK5OA==} @@ -10740,8 +10888,8 @@ packages: peerDependencies: react: '>=15' - react@19.2.5: - resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==} + react@19.2.6: + resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} engines: {node: '>=0.10.0'} read-cache@1.0.0: @@ -10813,8 +10961,8 @@ packages: resolution: {integrity: sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==} engines: {node: '>=4'} - registry-auth-token@5.1.0: - resolution: {integrity: sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==} + registry-auth-token@5.1.1: + resolution: {integrity: sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==} engines: {node: '>=14'} registry-url@6.0.1: @@ -10824,8 +10972,8 @@ packages: regjsgen@0.8.0: resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} - regjsparser@0.13.0: - resolution: {integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==} + regjsparser@0.13.1: + resolution: {integrity: sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==} hasBin: true rehype-parse@7.0.1: @@ -10910,8 +11058,8 @@ packages: resolve-protobuf-schema@2.1.0: resolution: {integrity: sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==} - resolve@1.22.11: - resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} engines: {node: '>= 0.4'} hasBin: true @@ -10951,8 +11099,8 @@ packages: robust-predicates@3.0.3: resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} - rolldown@1.0.0-rc.17: - resolution: {integrity: sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==} + rolldown@1.0.1: + resolution: {integrity: sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -10969,8 +11117,8 @@ packages: rollup: optional: true - rollup@4.55.1: - resolution: {integrity: sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==} + rollup@4.60.4: + resolution: {integrity: sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -11039,8 +11187,9 @@ packages: engines: {node: '>=14.0.0'} hasBin: true - sax@1.4.3: - resolution: {integrity: sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==} + sax@1.6.0: + resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} + engines: {node: '>=11.0.0'} saxes@6.0.0: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} @@ -11073,9 +11222,9 @@ packages: select-hose@2.0.0: resolution: {integrity: sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==} - selfsigned@2.4.1: - resolution: {integrity: sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==} - engines: {node: '>=10'} + selfsigned@5.5.0: + resolution: {integrity: sha512-ftnu3TW4+3eBfLRFnDEkzGxSF/10BJBkaLJuBHZX0kiPS7bRdlpZGu6YGt4KngMkdTwJE6MbjavFpqHvqVt+Ew==} + engines: {node: '>=18'} semver-diff@4.0.0: resolution: {integrity: sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==} @@ -11085,8 +11234,8 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.7.4: - resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + semver@7.8.0: + resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} engines: {node: '>=10'} hasBin: true @@ -11104,8 +11253,8 @@ packages: serve-handler@6.1.7: resolution: {integrity: sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg==} - serve-index@1.9.1: - resolution: {integrity: sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==} + serve-index@1.9.2: + resolution: {integrity: sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ==} engines: {node: '>= 0.8.0'} serve-static@1.16.3: @@ -11126,9 +11275,6 @@ packages: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} - setprototypeof@1.1.0: - resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==} - setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -11159,8 +11305,8 @@ packages: resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} engines: {node: '>= 0.4'} - side-channel-list@1.0.0: - resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} engines: {node: '>= 0.4'} side-channel-map@1.0.1: @@ -11191,8 +11337,8 @@ packages: simple-get@4.0.1: resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} - simple-icons@16.17.0: - resolution: {integrity: sha512-bRrGtzM6NLgxeMWmRcfDdrRksECk101lRrCn6jjj6qzUB6lQ+E5smnr52rqS1kLPmbLpS/g6iF463j50M4BT7A==} + simple-icons@16.20.0: + resolution: {integrity: sha512-2b7Xu+Zy+iwLiuLSAcWuxnM1ETNFy7JmxWSjh0Y/A8P3KGjFFcokbpWbUf/IivMNbR9WuZ/QQlirh5bGh3Q2vA==} engines: {node: '>=0.12.18'} sirv@2.0.4: @@ -11206,8 +11352,8 @@ packages: sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - sitemap@7.1.2: - resolution: {integrity: sha512-ARCqzHJ0p4gWt+j7NlU5eDlIO9+Rkr/JhPFZKKQ1l5GCus7rJH4UdrlVAh0xC/gDS/Qir2UMxqYNHtsKr2rpCw==} + sitemap@7.1.3: + resolution: {integrity: sha512-tAjEd+wt/YwnEbfNB2ht51ybBJxbEWwe5ki/Z//Wh0rpBFTCUSj46GnxUKEWzhfuJTsee8x3lybHxFgUMig2hw==} engines: {node: '>=12.0.0', npm: '>=5.6.0'} hasBin: true @@ -11226,22 +11372,18 @@ packages: slice-source@0.4.1: resolution: {integrity: sha512-YiuPbxpCj4hD9Qs06hGAz/OZhQ0eDuALN0lRWJez0eD/RevzKqGdUx1IOMUnXgpr+sXZLq3g8ERwbAH0bCb8vg==} - smart-buffer@4.2.0: - resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} - engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - snake-case@3.0.4: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} - socket.io-adapter@2.5.6: - resolution: {integrity: sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==} + socket.io-adapter@2.5.7: + resolution: {integrity: sha512-e0LyK91f3cUxTmv95/KzoLg47+zF+s/sbxRGDNsyG4dmIP8ZSX8ax6byOxfJXeNNtS/8AZlfD+uP7gBeR7DLlg==} socket.io-client@4.8.3: resolution: {integrity: sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==} engines: {node: '>=10.0.0'} - socket.io-parser@4.2.5: - resolution: {integrity: sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==} + socket.io-parser@4.2.6: + resolution: {integrity: sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==} engines: {node: '>=10.0.0'} socket.io@4.8.3: @@ -11251,14 +11393,6 @@ packages: sockjs@0.3.24: resolution: {integrity: sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==} - socks-proxy-agent@8.0.5: - resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} - engines: {node: '>= 14'} - - socks@2.8.7: - resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} - engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} - sort-css-media-queries@2.2.0: resolution: {integrity: sha512-0xtkGhWCC9MGt/EzgnvbbbKhqWjl1+/rncmhTh5qCpbYguXh6S/qwePfv/JQ8jePXXmqingylxoC49pCkSPIbA==} engines: {node: '>= 6.3.0'} @@ -11305,8 +11439,8 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - sql-formatter@15.7.3: - resolution: {integrity: sha512-5+zl9Nqg5aNjss0tb1G+StpC4dJKbjv3+g8CL/+V+00PfZop+2RKGyi53ScFl0dr+Dkx1LjmUO54Q3N7K3EtMw==} + sql-formatter@15.8.0: + resolution: {integrity: sha512-HnjdRHlSsO4Ap2erB5YXAvWggrnk/S4TezUn8zmpq9J/hEKn9+6gGaqiKPyDtI10Xf4zJmHYPREGjMjZmmP1fg==} hasBin: true srcset@4.0.0: @@ -11320,10 +11454,6 @@ packages: resolution: {integrity: sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==} engines: {node: '>=10.16.0'} - ssri@13.0.1: - resolution: {integrity: sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==} - engines: {node: ^20.17.0 || >=22.9.0} - stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -11447,8 +11577,8 @@ packages: peerDependencies: postcss: ^8.4.31 - stylis@4.3.6: - resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} + stylis@4.4.0: + resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==} sucrase@3.35.1: resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} @@ -11483,17 +11613,17 @@ packages: peerDependencies: svelte: '>= 3.43.1 < 6' - svelte-check@4.4.6: - resolution: {integrity: sha512-kP1zG81EWaFe9ZyTv4ZXv44Csi6Pkdpb7S3oj6m+K2ec/IcDg/a8LsFsnVLqm2nxtkSwsd5xPj/qFkTBgXHXjg==} + svelte-check@4.4.8: + resolution: {integrity: sha512-67adfgBox5eNSNIvIIwgFizKGdcRrGpiMoNO2obHcYuLz7iTa8Xgm/NGU3ntMFnNm8K1grFOIG6HhMLX/vcN8w==} engines: {node: '>= 18.0.0'} hasBin: true peerDependencies: svelte: ^4.0.0 || ^5.0.0-next.0 typescript: '>=5.0.0' - svelte-eslint-parser@1.6.0: - resolution: {integrity: sha512-qoB1ehychT6OxEtQAqc/guSqLS20SlA53Uijl7x375s8nlUT0lb9ol/gzraEEatQwsyPTJo87s2CmKL9Xab+Uw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0, pnpm: 10.30.3} + svelte-eslint-parser@1.6.1: + resolution: {integrity: sha512-hhvSH6kRj46UzrBVO5TaotD+Iuvruj5ccKBcO4wAhVcPTLmIc/c32D8UllBTYO0on4LzYuM0rNzf1lM/gBlkSQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0, pnpm: 10.33.0} peerDependencies: svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 peerDependenciesMeta: @@ -11559,25 +11689,20 @@ packages: peerDependencies: svelte: ^5.30.2 - svelte@5.55.2: - resolution: {integrity: sha512-z41M/hi0ZPTzrwVKLvB/R1/Oo08gL1uIib8HZ+FncqxxtY9MLb01emg2fqk+WLZ/lNrrtNDFh7BZLDxAHvMgLw==} + svelte@5.55.8: + resolution: {integrity: sha512-4D6lyrMHmDaZalQOEBMCWCCidyZjSnec14/oPn0k627G6goxcck9xqMwz1tFLlQz+ZFvtTTHfFOlUayuAz0z6Q==} engines: {node: '>=18'} svg-parser@2.0.4: resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} - svgo@3.3.2: - resolution: {integrity: sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==} + svgo@3.3.3: + resolution: {integrity: sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng==} engines: {node: '>=14.0.0'} hasBin: true - swagger-ui-dist@5.32.4: - resolution: {integrity: sha512-0AADFFQNJzExEN49SrD/34Nn9cxNxVLiydYl2MBwSZFPVXNkVwC/EFAjoezGGqE8oDegiDC+p47t8lKObCinMQ==} - - swr@2.3.8: - resolution: {integrity: sha512-gaCPRVoMq8WGDcWj9p4YWzCMPHzE0WNl6W8ADIx9c3JBEIdMkJGMzW+uzXvxHMltwcYACr9jP+32H8/hgwMR7w==} - peerDependencies: - react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + swagger-ui-dist@5.32.6: + resolution: {integrity: sha512-75ttZNaYCLoFPnozPZcTUU6mS3wKT8l7WLjU5zJSHFeJa23i5vtnze6IiCl4jDMPeQTXVXIgovq4M11NNfQvSA==} symbol-observable@4.0.0: resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} @@ -11590,8 +11715,8 @@ packages: resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} engines: {node: ^14.18.0 || >=16.0.0} - systeminformation@5.31.5: - resolution: {integrity: sha512-5SyLdip4/3alxD4Kh+63bUQTJmu7YMfYQTC+koZy7X73HgNqZSD2P4wOZQWtUncvPvcEmnfIjCoygN4MRoEejQ==} + systeminformation@5.31.6: + resolution: {integrity: sha512-Uv2b2uGGM6ns+26czgW2cYRabYdnswM0ddSOOlryHOaelzsmDSet1iM/NT7VOYxW8x/BW+HkY+b1Ve2pLTSGSA==} engines: {node: '>=8.0.0'} os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android] hasBin: true @@ -11612,8 +11737,8 @@ packages: '@eslint/css': optional: true - tailwind-merge@3.5.0: - resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==} + tailwind-merge@3.6.0: + resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==} tailwind-variants@3.2.2: resolution: {integrity: sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg==} @@ -11647,8 +11772,8 @@ packages: engines: {node: '>=14.0.0'} hasBin: true - tailwindcss@4.2.4: - resolution: {integrity: sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==} + tailwindcss@4.3.0: + resolution: {integrity: sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==} tapable@2.3.3: resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} @@ -11664,40 +11789,66 @@ packages: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} - tar-stream@3.1.8: - resolution: {integrity: sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==} + tar-stream@3.2.0: + resolution: {integrity: sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==} tar@6.2.1: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - tar@7.5.7: - resolution: {integrity: sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==} + tar@7.5.15: + resolution: {integrity: sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==} engines: {node: '>=18'} - deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me teex@1.0.1: resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} - terser-webpack-plugin@5.4.0: - resolution: {integrity: sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==} + terser-webpack-plugin@5.6.0: + resolution: {integrity: sha512-Eum+5ajkaOhf5KbM26osvv21kLD7BaGqQ1UA4Ami4arYwylmGUQTgHFpHDdmJod1q4QXa66p0to/FBKID+J1vA==} engines: {node: '>= 10.13.0'} peerDependencies: + '@minify-html/node': '*' '@swc/core': '*' + '@swc/css': '*' + '@swc/html': '*' + clean-css: '*' + cssnano: '*' + csso: '*' esbuild: '*' + html-minifier-terser: '*' + lightningcss: '*' + postcss: '*' uglify-js: '*' webpack: ^5.1.0 peerDependenciesMeta: + '@minify-html/node': + optional: true '@swc/core': optional: true + '@swc/css': + optional: true + '@swc/html': + optional: true + clean-css: + optional: true + cssnano: + optional: true + csso: + optional: true esbuild: optional: true + html-minifier-terser: + optional: true + lightningcss: + optional: true + postcss: + optional: true uglify-js: optional: true - terser@5.46.1: - resolution: {integrity: sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==} + terser@5.47.1: + resolution: {integrity: sha512-tPbLXTI6ohPASb/1YViL428oEHu6/qv1OxqYnfaonVCFHqx4+wCd95pHrQWsL5X4pl90CTyW9piSAsS2L0VoMw==} engines: {node: '>=10'} hasBin: true @@ -11722,8 +11873,8 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - thingies@2.5.0: - resolution: {integrity: sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==} + thingies@2.6.0: + resolution: {integrity: sha512-rMHRjmlFLM1R96UYPvpmnc3LYtdFrT33JIB7L9hetGue1qAPfn1N2LJeEjxUSidu1Iku+haLZXDuEXUHNGO/lg==} engines: {node: '>=10.18'} peerDependencies: tslib: ^2 @@ -11731,12 +11882,8 @@ packages: three@0.179.1: resolution: {integrity: sha512-5y/elSIQbrvKOISxpwXCR4sQqHtGiOI+MKLc3SsBdDXA2hz3Mdp3X59aUp8DyybMa34aeBwbFTpdoLJaUDEWSw==} - three@0.182.0: - resolution: {integrity: sha512-GbHabT+Irv+ihI1/f5kIIsZ+Ef9Sl5A1Y7imvS5RQjWgtTPfPnZ43JmlYI7NtCRDK9zir20lQpfg8/9Yd02OvQ==} - - throttleit@2.1.0: - resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==} - engines: {node: '>=18'} + three@0.184.0: + resolution: {integrity: sha512-wtTRjG92pM5eUg/KuUnHsqSAlPM296brTOcLgMRqEeylYTh/CdtvKUvCyyCQTzFuStieWxvZb8mVTMvdPyUpxg==} through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} @@ -11766,8 +11913,8 @@ packages: tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - tinyexec@1.1.1: - resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==} + tinyexec@1.1.2: + resolution: {integrity: sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==} engines: {node: '>=18'} tinyglobby@0.2.16: @@ -11868,6 +12015,11 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + tsc-alias@1.8.17: + resolution: {integrity: sha512-EIduCZHqbNwPm8BZYfq1aD7BQ697A4h6uSGMOFQfYGoQwfrYFTKwYfy9Bv42YxHkduVBcn9Zx0DkX111DKskyg==} + engines: {node: '>=16.20.2'} + hasBin: true + tsconfck@3.1.6: resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} engines: {node: ^18 || >=20} @@ -11886,6 +12038,9 @@ packages: resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} engines: {node: '>=6'} + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -11893,11 +12048,15 @@ packages: resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} engines: {node: '>=0.6.x'} - tsx@4.21.0: - resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + tsx@4.22.3: + resolution: {integrity: sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==} engines: {node: '>=18.0.0'} hasBin: true + tsyringe@4.10.0: + resolution: {integrity: sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==} + engines: {node: '>= 6.0.0'} + tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} @@ -11928,9 +12087,9 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} - type-is@2.0.1: - resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} - engines: {node: '>= 0.6'} + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} type@2.7.3: resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==} @@ -11941,8 +12100,8 @@ packages: typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - typescript-eslint@8.59.0: - resolution: {integrity: sha512-BU3ONW9X+v90EcCH9ZS6LMackcVtxRLlI3XrYyqZIwVSHIk7Qf7bFw1z0M9Q0IUxhTMZCf8piY9hTYaNEIASrw==} + typescript-eslint@8.59.4: + resolution: {integrity: sha512-Rw6+44QNFaXtgHSjPy+Kw8hrJniMYzR85E9yLmOLcfZ91/rz+JXQbDTCmc6ccxMPY6K6PgAq26f0JCBfR7LIPQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -11965,9 +12124,6 @@ packages: resolution: {integrity: sha512-OsqGhxyo/wGdLSXMSJxuMGN6H4gDnKz6Fb3IBm4bxZFMnyy0sdf6MN96Ie8tC6z/btdO+Bsy8guxlvLdwT076w==} hasBin: true - ufo@1.6.2: - resolution: {integrity: sha512-heMioaxBcG9+Znsda5Q8sQbWnLJSl98AFDXTO80wELWEzX3hordXsTdxrIfMQoO9IY1MEnoGoPjpoKpMj+Yx0Q==} - uglify-js@3.19.3: resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} engines: {node: '>=0.8.0'} @@ -11991,8 +12147,9 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - undici-types@7.19.2: - resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} + undici@6.25.0: + resolution: {integrity: sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==} + engines: {node: '>=18.17'} undici@7.25.0: resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==} @@ -12024,14 +12181,6 @@ packages: unified@9.2.2: resolution: {integrity: sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==} - unique-filename@5.0.0: - resolution: {integrity: sha512-2RaJTAvAb4owyjllTfXzFClJ7WsGxlykkPvCr9pA//LD9goVq+m4PPAeBgNodGZ7nSrntT/auWpJ6Y5IFXcfjg==} - engines: {node: ^20.17.0 || >=22.9.0} - - unique-slug@6.0.0: - resolution: {integrity: sha512-4Lup7Ezn8W3d52/xBhZBVdx323ckxa7DEvd9kPQHppTkLoJXw6ltrBCyj5pnrxj0qKDxYMJ56CoxNuFCscdTiw==} - engines: {node: ^20.17.0 || >=22.9.0} - unique-string@3.0.0: resolution: {integrity: sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==} engines: {node: '>=12'} @@ -12066,8 +12215,8 @@ packages: unist-util-visit@2.0.3: resolution: {integrity: sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==} - unist-util-visit@5.0.0: - resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} @@ -12119,11 +12268,6 @@ packages: urlpattern-polyfill@8.0.2: resolution: {integrity: sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==} - use-sync-external-store@1.6.0: - resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - utf8-byte-length@1.0.5: resolution: {integrity: sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==} @@ -12150,10 +12294,6 @@ packages: deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true - uuid@11.1.0: - resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} - hasBin: true - uuid@14.0.0: resolution: {integrity: sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==} hasBin: true @@ -12163,8 +12303,8 @@ packages: deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true - valibot@1.3.1: - resolution: {integrity: sha512-sfdRir/QFM0JaF22hqTroPc5xy4DimuGQVKFrzF1YfGwaS1nJot3Y8VqMdLO2Lg27fMzat2yD3pY5PbAYO39Gg==} + valibot@1.4.0: + resolution: {integrity: sha512-iC/x7fVcSyOwlm/VSt7RlHnzNGLGvR9GnxdifUeWoCJo0q4ZZvrVkIHC6faTlkxG47I2Y4UrFquPuVHCrOnrLg==} peerDependencies: typescript: '>=5' peerDependenciesMeta: @@ -12217,8 +12357,8 @@ packages: peerDependencies: vite: '*' - vite@7.3.2: - resolution: {integrity: sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==} + vite@7.3.3: + resolution: {integrity: sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -12257,13 +12397,13 @@ packages: yaml: optional: true - vite@8.0.10: - resolution: {integrity: sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==} + vite@8.0.13: + resolution: {integrity: sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.1.0 + '@vitejs/devtools': ^0.1.18 esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' less: ^4.0.0 @@ -12300,10 +12440,10 @@ packages: yaml: optional: true - vitefu@1.1.2: - resolution: {integrity: sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==} + vitefu@1.1.3: + resolution: {integrity: sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==} peerDependencies: - vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0 + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: vite: optional: true @@ -12342,20 +12482,20 @@ packages: jsdom: optional: true - vitest@4.1.5: - resolution: {integrity: sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==} + vitest@4.1.7: + resolution: {integrity: sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.5 - '@vitest/browser-preview': 4.1.5 - '@vitest/browser-webdriverio': 4.1.5 - '@vitest/coverage-istanbul': 4.1.5 - '@vitest/coverage-v8': 4.1.5 - '@vitest/ui': 4.1.5 + '@vitest/browser-playwright': 4.1.7 + '@vitest/browser-preview': 4.1.7 + '@vitest/browser-webdriverio': 4.1.7 + '@vitest/coverage-istanbul': 4.1.7 + '@vitest/coverage-v8': 4.1.7 + '@vitest/ui': 4.1.7 happy-dom: '*' jsdom: '*' vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -12383,26 +12523,6 @@ packages: jsdom: optional: true - vscode-jsonrpc@8.2.0: - resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} - engines: {node: '>=14.0.0'} - - vscode-languageserver-protocol@3.17.5: - resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} - - vscode-languageserver-textdocument@1.0.12: - resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} - - vscode-languageserver-types@3.17.5: - resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} - - vscode-languageserver@9.0.1: - resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} - hasBin: true - - vscode-uri@3.0.8: - resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} - w3c-keyname@2.2.8: resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} @@ -12447,8 +12567,8 @@ packages: webpack: optional: true - webpack-dev-server@5.2.2: - resolution: {integrity: sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==} + webpack-dev-server@5.2.4: + resolution: {integrity: sha512-GqDPGZN9bRqKBTkp4aWkobDDHMsrXKoGSdOH56smIri8qR0JG8gfL8/v/f/OZR3/OKXjG8uwJbFVhKm/FNU/UA==} engines: {node: '>= 18.12.0'} hasBin: true peerDependencies: @@ -12472,8 +12592,8 @@ packages: resolution: {integrity: sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==} engines: {node: '>=6'} - webpack-sources@3.3.4: - resolution: {integrity: sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==} + webpack-sources@3.4.1: + resolution: {integrity: sha512-eACpxRN02yaawnt+uUNIF7Qje6A9zArxBbcAJjK1PK3S9Ycg5jIuJ8pW4q8EMnwNZCEGltcjkRx1QzOxOkKD8A==} engines: {node: '>=10.13.0'} webpack-virtual-modules@0.6.2: @@ -12489,8 +12609,8 @@ packages: webpack-cli: optional: true - webpack@5.106.2: - resolution: {integrity: sha512-wGN3qcrBQIFmQ/c0AiOAQBvrZ5lmY8vbbMv4Mxfgzqd/B6+9pXtLo73WuS1dSGXM5QYY3hZnIbvx+K1xxe6FyA==} + webpack@5.107.0: + resolution: {integrity: sha512-PSxeHk/dmLYZlnTU+vL1Gej6Evg5RNtl3flhxBresfznFnzxinHMzHKloHnywM/3ouQv7/AlZCswWDIkNSggUA==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -12615,20 +12735,8 @@ packages: utf-8-validate: optional: true - ws@8.18.3: - resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - ws@8.20.0: - resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} + ws@8.20.1: + resolution: {integrity: sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -12691,8 +12799,8 @@ packages: resolution: {integrity: sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==} engines: {node: '>= 6'} - yaml@2.8.3: - resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==} + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} engines: {node: '>= 14.6'} hasBin: true @@ -12756,141 +12864,135 @@ snapshots: '@adobe/css-tools@4.4.4': {} - '@ai-sdk/gateway@2.0.21(zod@4.3.6)': + '@algolia/abtesting@1.18.1': dependencies: - '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.19(zod@4.3.6) - '@vercel/oidc': 3.0.5 - zod: 4.3.6 + '@algolia/client-common': 5.52.1 + '@algolia/requester-browser-xhr': 5.52.1 + '@algolia/requester-fetch': 5.52.1 + '@algolia/requester-node-http': 5.52.1 - '@ai-sdk/provider-utils@3.0.19(zod@4.3.6)': + '@algolia/autocomplete-core@1.19.2(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)(search-insights@2.17.3)': dependencies: - '@ai-sdk/provider': 2.0.0 - '@standard-schema/spec': 1.1.0 - eventsource-parser: 3.0.6 - zod: 4.3.6 - - '@ai-sdk/provider@2.0.0': - dependencies: - json-schema: 0.4.0 - - '@ai-sdk/react@2.0.115(react@19.2.5)(zod@4.3.6)': - dependencies: - '@ai-sdk/provider-utils': 3.0.19(zod@4.3.6) - ai: 5.0.113(zod@4.3.6) - react: 19.2.5 - swr: 2.3.8(react@19.2.5) - throttleit: 2.1.0 - optionalDependencies: - zod: 4.3.6 - - '@algolia/abtesting@1.12.0': - dependencies: - '@algolia/client-common': 5.46.0 - '@algolia/requester-browser-xhr': 5.46.0 - '@algolia/requester-fetch': 5.46.0 - '@algolia/requester-node-http': 5.46.0 - - '@algolia/autocomplete-core@1.19.2(@algolia/client-search@5.46.0)(algoliasearch@5.46.0)(search-insights@2.17.3)': - dependencies: - '@algolia/autocomplete-plugin-algolia-insights': 1.19.2(@algolia/client-search@5.46.0)(algoliasearch@5.46.0)(search-insights@2.17.3) - '@algolia/autocomplete-shared': 1.19.2(@algolia/client-search@5.46.0)(algoliasearch@5.46.0) + '@algolia/autocomplete-plugin-algolia-insights': 1.19.2(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)(search-insights@2.17.3) + '@algolia/autocomplete-shared': 1.19.2(@algolia/client-search@5.52.1)(algoliasearch@5.52.1) transitivePeerDependencies: - '@algolia/client-search' - algoliasearch - search-insights - '@algolia/autocomplete-plugin-algolia-insights@1.19.2(@algolia/client-search@5.46.0)(algoliasearch@5.46.0)(search-insights@2.17.3)': + '@algolia/autocomplete-core@1.19.8(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)(search-insights@2.17.3)': dependencies: - '@algolia/autocomplete-shared': 1.19.2(@algolia/client-search@5.46.0)(algoliasearch@5.46.0) + '@algolia/autocomplete-plugin-algolia-insights': 1.19.8(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)(search-insights@2.17.3) + '@algolia/autocomplete-shared': 1.19.8(@algolia/client-search@5.52.1)(algoliasearch@5.52.1) + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch + - search-insights + + '@algolia/autocomplete-plugin-algolia-insights@1.19.2(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)(search-insights@2.17.3)': + dependencies: + '@algolia/autocomplete-shared': 1.19.2(@algolia/client-search@5.52.1)(algoliasearch@5.52.1) search-insights: 2.17.3 transitivePeerDependencies: - '@algolia/client-search' - algoliasearch - '@algolia/autocomplete-shared@1.19.2(@algolia/client-search@5.46.0)(algoliasearch@5.46.0)': + '@algolia/autocomplete-plugin-algolia-insights@1.19.8(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)(search-insights@2.17.3)': dependencies: - '@algolia/client-search': 5.46.0 - algoliasearch: 5.46.0 + '@algolia/autocomplete-shared': 1.19.8(@algolia/client-search@5.52.1)(algoliasearch@5.52.1) + search-insights: 2.17.3 + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch - '@algolia/client-abtesting@5.46.0': + '@algolia/autocomplete-shared@1.19.2(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)': dependencies: - '@algolia/client-common': 5.46.0 - '@algolia/requester-browser-xhr': 5.46.0 - '@algolia/requester-fetch': 5.46.0 - '@algolia/requester-node-http': 5.46.0 + '@algolia/client-search': 5.52.1 + algoliasearch: 5.52.1 - '@algolia/client-analytics@5.46.0': + '@algolia/autocomplete-shared@1.19.8(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)': dependencies: - '@algolia/client-common': 5.46.0 - '@algolia/requester-browser-xhr': 5.46.0 - '@algolia/requester-fetch': 5.46.0 - '@algolia/requester-node-http': 5.46.0 + '@algolia/client-search': 5.52.1 + algoliasearch: 5.52.1 - '@algolia/client-common@5.46.0': {} - - '@algolia/client-insights@5.46.0': + '@algolia/client-abtesting@5.52.1': dependencies: - '@algolia/client-common': 5.46.0 - '@algolia/requester-browser-xhr': 5.46.0 - '@algolia/requester-fetch': 5.46.0 - '@algolia/requester-node-http': 5.46.0 + '@algolia/client-common': 5.52.1 + '@algolia/requester-browser-xhr': 5.52.1 + '@algolia/requester-fetch': 5.52.1 + '@algolia/requester-node-http': 5.52.1 - '@algolia/client-personalization@5.46.0': + '@algolia/client-analytics@5.52.1': dependencies: - '@algolia/client-common': 5.46.0 - '@algolia/requester-browser-xhr': 5.46.0 - '@algolia/requester-fetch': 5.46.0 - '@algolia/requester-node-http': 5.46.0 + '@algolia/client-common': 5.52.1 + '@algolia/requester-browser-xhr': 5.52.1 + '@algolia/requester-fetch': 5.52.1 + '@algolia/requester-node-http': 5.52.1 - '@algolia/client-query-suggestions@5.46.0': - dependencies: - '@algolia/client-common': 5.46.0 - '@algolia/requester-browser-xhr': 5.46.0 - '@algolia/requester-fetch': 5.46.0 - '@algolia/requester-node-http': 5.46.0 + '@algolia/client-common@5.52.1': {} - '@algolia/client-search@5.46.0': + '@algolia/client-insights@5.52.1': dependencies: - '@algolia/client-common': 5.46.0 - '@algolia/requester-browser-xhr': 5.46.0 - '@algolia/requester-fetch': 5.46.0 - '@algolia/requester-node-http': 5.46.0 + '@algolia/client-common': 5.52.1 + '@algolia/requester-browser-xhr': 5.52.1 + '@algolia/requester-fetch': 5.52.1 + '@algolia/requester-node-http': 5.52.1 + + '@algolia/client-personalization@5.52.1': + dependencies: + '@algolia/client-common': 5.52.1 + '@algolia/requester-browser-xhr': 5.52.1 + '@algolia/requester-fetch': 5.52.1 + '@algolia/requester-node-http': 5.52.1 + + '@algolia/client-query-suggestions@5.52.1': + dependencies: + '@algolia/client-common': 5.52.1 + '@algolia/requester-browser-xhr': 5.52.1 + '@algolia/requester-fetch': 5.52.1 + '@algolia/requester-node-http': 5.52.1 + + '@algolia/client-search@5.52.1': + dependencies: + '@algolia/client-common': 5.52.1 + '@algolia/requester-browser-xhr': 5.52.1 + '@algolia/requester-fetch': 5.52.1 + '@algolia/requester-node-http': 5.52.1 '@algolia/events@4.0.1': {} - '@algolia/ingestion@1.46.0': + '@algolia/ingestion@1.52.1': dependencies: - '@algolia/client-common': 5.46.0 - '@algolia/requester-browser-xhr': 5.46.0 - '@algolia/requester-fetch': 5.46.0 - '@algolia/requester-node-http': 5.46.0 + '@algolia/client-common': 5.52.1 + '@algolia/requester-browser-xhr': 5.52.1 + '@algolia/requester-fetch': 5.52.1 + '@algolia/requester-node-http': 5.52.1 - '@algolia/monitoring@1.46.0': + '@algolia/monitoring@1.52.1': dependencies: - '@algolia/client-common': 5.46.0 - '@algolia/requester-browser-xhr': 5.46.0 - '@algolia/requester-fetch': 5.46.0 - '@algolia/requester-node-http': 5.46.0 + '@algolia/client-common': 5.52.1 + '@algolia/requester-browser-xhr': 5.52.1 + '@algolia/requester-fetch': 5.52.1 + '@algolia/requester-node-http': 5.52.1 - '@algolia/recommend@5.46.0': + '@algolia/recommend@5.52.1': dependencies: - '@algolia/client-common': 5.46.0 - '@algolia/requester-browser-xhr': 5.46.0 - '@algolia/requester-fetch': 5.46.0 - '@algolia/requester-node-http': 5.46.0 + '@algolia/client-common': 5.52.1 + '@algolia/requester-browser-xhr': 5.52.1 + '@algolia/requester-fetch': 5.52.1 + '@algolia/requester-node-http': 5.52.1 - '@algolia/requester-browser-xhr@5.46.0': + '@algolia/requester-browser-xhr@5.52.1': dependencies: - '@algolia/client-common': 5.46.0 + '@algolia/client-common': 5.52.1 - '@algolia/requester-fetch@5.46.0': + '@algolia/requester-fetch@5.52.1': dependencies: - '@algolia/client-common': 5.46.0 + '@algolia/client-common': 5.52.1 - '@algolia/requester-node-http@5.46.0': + '@algolia/requester-node-http@5.52.1': dependencies: - '@algolia/client-common': 5.46.0 + '@algolia/client-common': 5.52.1 '@alloc/quick-lru@5.2.0': {} @@ -12910,11 +13012,11 @@ snapshots: optionalDependencies: chokidar: 4.0.3 - '@angular-devkit/schematics-cli@19.2.24(@types/node@24.12.2)(chokidar@4.0.3)': + '@angular-devkit/schematics-cli@19.2.24(@types/node@24.12.4)(chokidar@4.0.3)': dependencies: '@angular-devkit/core': 19.2.24(chokidar@4.0.3) '@angular-devkit/schematics': 19.2.24(chokidar@4.0.3) - '@inquirer/prompts': 7.3.2(@types/node@24.12.2) + '@inquirer/prompts': 7.3.2(@types/node@24.12.4) ansi-colors: 4.1.3 symbol-observable: 4.0.0 yargs-parser: 21.1.1 @@ -12935,7 +13037,7 @@ snapshots: '@antfu/install-pkg@1.1.0': dependencies: package-manager-detector: 1.6.0 - tinyexec: 1.1.1 + tinyexec: 1.1.2 '@asamuzakjp/css-color@3.2.0': dependencies: @@ -12952,16 +13054,16 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.28.5': {} + '@babel/compat-data@7.29.3': {} - '@babel/core@7.28.5': + '@babel/core@7.29.0': dependencies: '@babel/code-frame': 7.29.0 '@babel/generator': 7.29.1 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) - '@babel/helpers': 7.28.4 - '@babel/parser': 7.29.2 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.29.2 + '@babel/parser': 7.29.3 '@babel/template': 7.28.6 '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 @@ -12976,7 +13078,7 @@ snapshots: '@babel/generator@7.29.1': dependencies: - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/types': 7.29.0 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 @@ -12986,42 +13088,42 @@ snapshots: dependencies: '@babel/types': 7.29.0 - '@babel/helper-compilation-targets@7.27.2': + '@babel/helper-compilation-targets@7.28.6': dependencies: - '@babel/compat-data': 7.28.5 + '@babel/compat-data': 7.29.3 '@babel/helper-validator-option': 7.27.1 browserslist: 4.28.2 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.28.5(@babel/core@7.28.5)': + '@babel/helper-create-class-features-plugin@7.29.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-member-expression-to-functions': 7.28.5 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.5) + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 '@babel/traverse': 7.29.0 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.28.5)': + '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 regexpu-core: 6.4.0 semver: 6.3.1 - '@babel/helper-define-polyfill-provider@0.6.5(@babel/core@7.28.5)': + '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 debug: 4.4.3 lodash.debounce: 4.0.8 - resolve: 1.22.11 + resolve: 1.22.12 transitivePeerDependencies: - supports-color @@ -13034,17 +13136,17 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-module-imports@7.27.1': + '@babel/helper-module-imports@7.28.6': dependencies: '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-imports': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 '@babel/helper-validator-identifier': 7.28.5 '@babel/traverse': 7.29.0 transitivePeerDependencies: @@ -13054,20 +13156,20 @@ snapshots: dependencies: '@babel/types': 7.29.0 - '@babel/helper-plugin-utils@7.27.1': {} + '@babel/helper-plugin-utils@7.28.6': {} - '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.28.5)': + '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-wrap-function': 7.28.3 + '@babel/helper-wrap-function': 7.28.6 '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.5)': + '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-member-expression-to-functions': 7.28.5 '@babel/helper-optimise-call-expression': 7.27.1 '@babel/traverse': 7.29.0 @@ -13087,7 +13189,7 @@ snapshots: '@babel/helper-validator-option@7.27.1': {} - '@babel/helper-wrap-function@7.28.3': + '@babel/helper-wrap-function@7.28.6': dependencies: '@babel/template': 7.28.6 '@babel/traverse': 7.29.0 @@ -13095,7 +13197,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helpers@7.28.4': + '@babel/helpers@7.29.2': dependencies: '@babel/template': 7.28.6 '@babel/types': 7.29.0 @@ -13104,573 +13206,582 @@ snapshots: dependencies: '@babel/types': 7.29.0 - '@babel/parser@7.29.2': + '@babel/parser@7.29.3': dependencies: '@babel/types': 7.29.0 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-transform-optional-chaining': 7.28.5(@babel/core@7.28.5) transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.3(@babel/core@7.28.5)': + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.5)': + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 - '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.28.5)': + '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-assertions@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.28.5)': + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-async-generator-functions@7.28.0(@babel/core@7.28.5)': + '@babel/plugin-transform-async-generator-functions@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-async-to-generator@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-block-scoping@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-class-properties@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-class-static-block@7.28.3(@babel/core@7.28.5)': + '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-classes@7.28.4(@babel/core@7.28.5)': + '@babel/plugin-transform-classes@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-globals': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-computed-properties@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 '@babel/template': 7.28.6 - '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-dotall-regex@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-explicit-resource-management@7.28.0(@babel/core@7.28.5)': + '@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-exponentiation-operator@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-json-strings@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-literals@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-literals@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-logical-assignment-operators@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-systemjs@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-modules-systemjs@7.29.4(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-identifier': 7.28.5 '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-named-capturing-groups-regex@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-nullish-coalescing-operator@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-numeric-separator@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-object-rest-spread@7.28.4(@babel/core@7.28.5)': + '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-optional-catch-binding@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-optional-chaining@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.28.5)': + '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-private-methods@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-private-property-in-object@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-constant-elements@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-react-constant-elements@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.28.5)': + '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-react-jsx@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-regenerator@7.28.4(@babel/core@7.28.5)': + '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-regexp-modifiers@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-regexp-modifiers@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-runtime@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-runtime@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-plugin-utils': 7.27.1 - babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.28.5) - babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.28.5) - babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.0) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.29.0) + babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.0) semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-spread@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-spread@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-typescript@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-property-regex@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-unicode-property-regex@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-sets-regex@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-unicode-sets-regex@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/preset-env@7.28.5(@babel/core@7.28.5)': + '@babel/preset-env@7.29.5(@babel/core@7.29.0)': dependencies: - '@babel/compat-data': 7.28.5 - '@babel/core': 7.28.5 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/compat-data': 7.29.3 + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.3(@babel/core@7.28.5) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.5) - '@babel/plugin-syntax-import-assertions': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.28.5) - '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-async-generator-functions': 7.28.0(@babel/core@7.28.5) - '@babel/plugin-transform-async-to-generator': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-block-scoping': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-transform-class-properties': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-class-static-block': 7.28.3(@babel/core@7.28.5) - '@babel/plugin-transform-classes': 7.28.4(@babel/core@7.28.5) - '@babel/plugin-transform-computed-properties': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-transform-dotall-regex': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-explicit-resource-management': 7.28.0(@babel/core@7.28.5) - '@babel/plugin-transform-exponentiation-operator': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-json-strings': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-logical-assignment-operators': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-modules-systemjs': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-nullish-coalescing-operator': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-numeric-separator': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-object-rest-spread': 7.28.4(@babel/core@7.28.5) - '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-optional-catch-binding': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-optional-chaining': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.5) - '@babel/plugin-transform-private-methods': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-private-property-in-object': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-regenerator': 7.28.4(@babel/core@7.28.5) - '@babel/plugin-transform-regexp-modifiers': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-spread': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-unicode-property-regex': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-unicode-sets-regex': 7.27.1(@babel/core@7.28.5) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.28.5) - babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.28.5) - babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.28.5) - babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.28.5) + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array': 7.29.3(@babel/core@7.29.0) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0) + '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.0) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-modules-systemjs': 7.29.4(@babel/core@7.29.0) + '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) + '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-regexp-modifiers': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-property-regex': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.29.0) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.0) + babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.0) + babel-plugin-polyfill-corejs3: 0.14.2(@babel/core@7.29.0) + babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.0) core-js-compat: 3.49.0 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.28.5)': + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 '@babel/types': 7.29.0 esutils: 2.0.3 - '@babel/preset-react@7.28.5(@babel/core@7.28.5)': + '@babel/preset-react@7.28.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.28.5) - '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/preset-typescript@7.28.5(@babel/core@7.28.5)': + '@babel/preset-typescript@7.28.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.5) + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) transitivePeerDependencies: - supports-color @@ -13679,14 +13790,14 @@ snapshots: '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.29.0 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/types': 7.29.0 '@babel/traverse@7.27.0': dependencies: '@babel/code-frame': 7.29.0 '@babel/generator': 7.29.1 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/template': 7.28.6 '@babel/types': 7.29.0 debug: 4.4.3 @@ -13699,7 +13810,7 @@ snapshots: '@babel/code-frame': 7.29.0 '@babel/generator': 7.29.1 '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/template': 7.28.6 '@babel/types': 7.29.0 debug: 4.4.3 @@ -13717,37 +13828,22 @@ snapshots: '@borewit/text-codec@0.2.2': {} - '@braintree/sanitize-url@7.1.1': {} + '@braintree/sanitize-url@7.1.2': {} - '@chevrotain/cst-dts-gen@11.0.3': - dependencies: - '@chevrotain/gast': 11.0.3 - '@chevrotain/types': 11.0.3 - lodash-es: 4.17.21 + '@chevrotain/types@11.1.2': {} - '@chevrotain/gast@11.0.3': - dependencies: - '@chevrotain/types': 11.0.3 - lodash-es: 4.17.21 - - '@chevrotain/regexp-to-ast@11.0.3': {} - - '@chevrotain/types@11.0.3': {} - - '@chevrotain/utils@11.0.3': {} - - '@codemirror/autocomplete@6.20.1': + '@codemirror/autocomplete@6.20.2': dependencies: '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 - '@codemirror/view': 6.41.1 + '@codemirror/view': 6.43.0 '@lezer/common': 1.5.2 '@codemirror/commands@6.10.3': dependencies: '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 - '@codemirror/view': 6.41.1 + '@codemirror/view': 6.43.0 '@lezer/common': 1.5.2 '@codemirror/lang-json@6.0.2': @@ -13758,29 +13854,29 @@ snapshots: '@codemirror/language@6.12.3': dependencies: '@codemirror/state': 6.6.0 - '@codemirror/view': 6.41.1 + '@codemirror/view': 6.43.0 '@lezer/common': 1.5.2 '@lezer/highlight': 1.2.3 '@lezer/lr': 1.4.10 style-mod: 4.1.3 - '@codemirror/lint@6.9.5': + '@codemirror/lint@6.9.6': dependencies: '@codemirror/state': 6.6.0 - '@codemirror/view': 6.41.1 + '@codemirror/view': 6.43.0 crelt: 1.0.6 '@codemirror/search@6.7.0': dependencies: '@codemirror/state': 6.6.0 - '@codemirror/view': 6.41.1 + '@codemirror/view': 6.43.0 crelt: 1.0.6 '@codemirror/state@6.6.0': dependencies: '@marijn/find-cluster-break': 1.0.2 - '@codemirror/view@6.41.1': + '@codemirror/view@6.43.0': dependencies: '@codemirror/state': 6.6.0 crelt: 1.0.6 @@ -13820,261 +13916,272 @@ snapshots: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-alpha-function@1.0.1(postcss@8.5.12)': + '@csstools/postcss-alpha-function@1.0.1(postcss@8.5.15)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - '@csstools/postcss-cascade-layers@5.0.2(postcss@8.5.12)': + '@csstools/postcss-cascade-layers@5.0.2(postcss@8.5.15)': dependencies: '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.1) - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 - '@csstools/postcss-color-function-display-p3-linear@1.0.1(postcss@8.5.12)': + '@csstools/postcss-color-function-display-p3-linear@1.0.1(postcss@8.5.15)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - '@csstools/postcss-color-function@4.0.12(postcss@8.5.12)': + '@csstools/postcss-color-function@4.0.12(postcss@8.5.15)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - '@csstools/postcss-color-mix-function@3.0.12(postcss@8.5.12)': + '@csstools/postcss-color-mix-function@3.0.12(postcss@8.5.15)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - '@csstools/postcss-color-mix-variadic-function-arguments@1.0.2(postcss@8.5.12)': + '@csstools/postcss-color-mix-variadic-function-arguments@1.0.2(postcss@8.5.15)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - '@csstools/postcss-content-alt-text@2.0.8(postcss@8.5.12)': + '@csstools/postcss-content-alt-text@2.0.8(postcss@8.5.15)': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - '@csstools/postcss-contrast-color-function@2.0.12(postcss@8.5.12)': + '@csstools/postcss-contrast-color-function@2.0.12(postcss@8.5.15)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - '@csstools/postcss-exponential-functions@2.0.9(postcss@8.5.12)': + '@csstools/postcss-exponential-functions@2.0.9(postcss@8.5.15)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.12 + postcss: 8.5.15 - '@csstools/postcss-font-format-keywords@4.0.0(postcss@8.5.12)': + '@csstools/postcss-font-format-keywords@4.0.0(postcss@8.5.15)': dependencies: - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 postcss-value-parser: 4.2.0 - '@csstools/postcss-gamut-mapping@2.0.11(postcss@8.5.12)': + '@csstools/postcss-gamut-mapping@2.0.11(postcss@8.5.15)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.12 + postcss: 8.5.15 - '@csstools/postcss-gradients-interpolation-method@5.0.12(postcss@8.5.12)': + '@csstools/postcss-gradients-interpolation-method@5.0.12(postcss@8.5.15)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - '@csstools/postcss-hwb-function@4.0.12(postcss@8.5.12)': + '@csstools/postcss-hwb-function@4.0.12(postcss@8.5.15)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - '@csstools/postcss-ic-unit@4.0.4(postcss@8.5.12)': + '@csstools/postcss-ic-unit@4.0.4(postcss@8.5.15)': dependencies: - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 postcss-value-parser: 4.2.0 - '@csstools/postcss-initial@2.0.1(postcss@8.5.12)': + '@csstools/postcss-initial@2.0.1(postcss@8.5.15)': dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - '@csstools/postcss-is-pseudo-class@5.0.3(postcss@8.5.12)': + '@csstools/postcss-is-pseudo-class@5.0.3(postcss@8.5.15)': dependencies: '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.1) - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 - '@csstools/postcss-light-dark-function@2.0.11(postcss@8.5.12)': + '@csstools/postcss-light-dark-function@2.0.11(postcss@8.5.15)': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - '@csstools/postcss-logical-float-and-clear@3.0.0(postcss@8.5.12)': + '@csstools/postcss-logical-float-and-clear@3.0.0(postcss@8.5.15)': dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - '@csstools/postcss-logical-overflow@2.0.0(postcss@8.5.12)': + '@csstools/postcss-logical-overflow@2.0.0(postcss@8.5.15)': dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - '@csstools/postcss-logical-overscroll-behavior@2.0.0(postcss@8.5.12)': + '@csstools/postcss-logical-overscroll-behavior@2.0.0(postcss@8.5.15)': dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - '@csstools/postcss-logical-resize@3.0.0(postcss@8.5.12)': + '@csstools/postcss-logical-resize@3.0.0(postcss@8.5.15)': dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - '@csstools/postcss-logical-viewport-units@3.0.4(postcss@8.5.12)': + '@csstools/postcss-logical-viewport-units@3.0.4(postcss@8.5.15)': dependencies: '@csstools/css-tokenizer': 3.0.4 - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - '@csstools/postcss-media-minmax@2.0.9(postcss@8.5.12)': + '@csstools/postcss-media-minmax@2.0.9(postcss@8.5.15)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 '@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - postcss: 8.5.12 + postcss: 8.5.15 - '@csstools/postcss-media-queries-aspect-ratio-number-values@3.0.5(postcss@8.5.12)': + '@csstools/postcss-media-queries-aspect-ratio-number-values@3.0.5(postcss@8.5.15)': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 '@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - postcss: 8.5.12 + postcss: 8.5.15 - '@csstools/postcss-nested-calc@4.0.0(postcss@8.5.12)': + '@csstools/postcss-nested-calc@4.0.0(postcss@8.5.15)': dependencies: - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 postcss-value-parser: 4.2.0 - '@csstools/postcss-normalize-display-values@4.0.0(postcss@8.5.12)': + '@csstools/postcss-normalize-display-values@4.0.1(postcss@8.5.15)': dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - '@csstools/postcss-oklab-function@4.0.12(postcss@8.5.12)': + '@csstools/postcss-oklab-function@4.0.12(postcss@8.5.15)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - '@csstools/postcss-position-area-property@1.0.0(postcss@8.5.12)': + '@csstools/postcss-position-area-property@1.0.0(postcss@8.5.15)': dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - '@csstools/postcss-progressive-custom-properties@4.2.1(postcss@8.5.12)': + '@csstools/postcss-progressive-custom-properties@4.2.1(postcss@8.5.15)': dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - '@csstools/postcss-random-function@2.0.1(postcss@8.5.12)': + '@csstools/postcss-property-rule-prelude-list@1.0.0(postcss@8.5.15)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + postcss: 8.5.15 + + '@csstools/postcss-random-function@2.0.1(postcss@8.5.15)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.12 + postcss: 8.5.15 - '@csstools/postcss-relative-color-syntax@3.0.12(postcss@8.5.12)': + '@csstools/postcss-relative-color-syntax@3.0.12(postcss@8.5.15)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - '@csstools/postcss-scope-pseudo-class@4.0.1(postcss@8.5.12)': + '@csstools/postcss-scope-pseudo-class@4.0.1(postcss@8.5.15)': dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 - '@csstools/postcss-sign-functions@1.1.4(postcss@8.5.12)': + '@csstools/postcss-sign-functions@1.1.4(postcss@8.5.15)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.12 + postcss: 8.5.15 - '@csstools/postcss-stepped-value-functions@4.0.9(postcss@8.5.12)': + '@csstools/postcss-stepped-value-functions@4.0.9(postcss@8.5.15)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.12 + postcss: 8.5.15 - '@csstools/postcss-system-ui-font-family@1.0.0(postcss@8.5.12)': + '@csstools/postcss-syntax-descriptor-syntax-production@1.0.1(postcss@8.5.15)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + postcss: 8.5.15 + + '@csstools/postcss-system-ui-font-family@1.0.0(postcss@8.5.15)': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.12 + postcss: 8.5.15 - '@csstools/postcss-text-decoration-shorthand@4.0.3(postcss@8.5.12)': + '@csstools/postcss-text-decoration-shorthand@4.0.3(postcss@8.5.15)': dependencies: '@csstools/color-helpers': 5.1.0 - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - '@csstools/postcss-trigonometric-functions@4.0.9(postcss@8.5.12)': + '@csstools/postcss-trigonometric-functions@4.0.9(postcss@8.5.15)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.12 + postcss: 8.5.15 - '@csstools/postcss-unset-value@4.0.0(postcss@8.5.12)': + '@csstools/postcss-unset-value@4.0.0(postcss@8.5.15)': dependencies: - postcss: 8.5.12 + postcss: 8.5.15 '@csstools/selector-resolve-nested@3.1.0(postcss-selector-parser@7.1.1)': dependencies: @@ -14084,94 +14191,135 @@ snapshots: dependencies: postcss-selector-parser: 7.1.1 - '@csstools/utilities@2.0.0(postcss@8.5.12)': + '@csstools/utilities@2.0.0(postcss@8.5.15)': dependencies: - postcss: 8.5.12 + postcss: 8.5.15 '@discoveryjs/json-ext@0.5.7': {} - '@docsearch/core@4.3.1(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@docsearch/core@4.6.3(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': optionalDependencies: - '@types/react': 19.2.14 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@types/react': 19.2.15 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) - '@docsearch/css@4.3.2': {} + '@docsearch/css@4.6.3': {} - '@docsearch/react@4.3.2(@algolia/client-search@5.46.0)(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(search-insights@2.17.3)': + '@docsearch/react@4.6.3(@algolia/client-search@5.52.1)(@types/react@19.2.15)(algoliasearch@5.52.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(search-insights@2.17.3)': dependencies: - '@ai-sdk/react': 2.0.115(react@19.2.5)(zod@4.3.6) - '@algolia/autocomplete-core': 1.19.2(@algolia/client-search@5.46.0)(algoliasearch@5.46.0)(search-insights@2.17.3) - '@docsearch/core': 4.3.1(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docsearch/css': 4.3.2 - ai: 5.0.113(zod@4.3.6) - algoliasearch: 5.46.0 - marked: 16.4.2 - zod: 4.3.6 + '@algolia/autocomplete-core': 1.19.2(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)(search-insights@2.17.3) + '@docsearch/core': 4.6.3(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docsearch/css': 4.6.3 optionalDependencies: - '@types/react': 19.2.14 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@types/react': 19.2.15 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) search-insights: 2.17.3 transitivePeerDependencies: - '@algolia/client-search' + - algoliasearch - '@docusaurus/babel@3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@docusaurus/babel@3.10.1(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/generator': 7.29.1 - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-transform-runtime': 7.28.5(@babel/core@7.28.5) - '@babel/preset-env': 7.28.5(@babel/core@7.28.5) - '@babel/preset-react': 7.28.5(@babel/core@7.28.5) - '@babel/preset-typescript': 7.28.5(@babel/core@7.28.5) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-transform-runtime': 7.29.0(@babel/core@7.29.0) + '@babel/preset-env': 7.29.5(@babel/core@7.29.0) + '@babel/preset-react': 7.28.5(@babel/core@7.29.0) + '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) '@babel/runtime': 7.29.2 '@babel/traverse': 7.29.0 - '@docusaurus/logger': 3.10.0 - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/logger': 3.10.1 + '@docusaurus/utils': 3.10.1(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) babel-plugin-dynamic-import-node: 2.3.3 - fs-extra: 11.3.4 + fs-extra: 11.3.5 tslib: 2.8.1 transitivePeerDependencies: + - '@minify-html/node' - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso - esbuild + - html-minifier-terser + - lightningcss + - postcss - react - react-dom - supports-color - uglify-js - webpack-cli - '@docusaurus/bundler@3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3)': + '@docusaurus/babel@3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@babel/core': 7.28.5 - '@docusaurus/babel': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/cssnano-preset': 3.10.0 - '@docusaurus/logger': 3.10.0 - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - babel-loader: 9.2.1(@babel/core@7.28.5)(webpack@5.106.2) - clean-css: 5.3.3 - copy-webpack-plugin: 11.0.0(webpack@5.106.2) - css-loader: 6.11.0(webpack@5.106.2) - css-minimizer-webpack-plugin: 5.0.1(clean-css@5.3.3)(webpack@5.106.2) - cssnano: 6.1.2(postcss@8.5.12) - file-loader: 6.2.0(webpack@5.106.2) - html-minifier-terser: 7.2.0 - mini-css-extract-plugin: 2.9.4(webpack@5.106.2) - null-loader: 4.0.1(webpack@5.106.2) - postcss: 8.5.12 - postcss-loader: 7.3.4(postcss@8.5.12)(typescript@6.0.3)(webpack@5.106.2) - postcss-preset-env: 10.5.0(postcss@8.5.12) - terser-webpack-plugin: 5.4.0(webpack@5.106.2) + '@babel/core': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-transform-runtime': 7.29.0(@babel/core@7.29.0) + '@babel/preset-env': 7.29.5(@babel/core@7.29.0) + '@babel/preset-react': 7.28.5(@babel/core@7.29.0) + '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) + '@babel/runtime': 7.29.2 + '@babel/traverse': 7.29.0 + '@docusaurus/logger': 3.10.1 + '@docusaurus/utils': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + babel-plugin-dynamic-import-node: 2.3.3 + fs-extra: 11.3.5 tslib: 2.8.1 - url-loader: 4.1.1(file-loader@6.2.0(webpack@5.106.2))(webpack@5.106.2) - webpack: 5.106.2 - webpackbar: 7.0.0(webpack@5.106.2) transitivePeerDependencies: + - '@minify-html/node' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - react + - react-dom + - supports-color + - uglify-js + - webpack-cli + + '@docusaurus/bundler@3.10.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)': + dependencies: + '@babel/core': 7.29.0 + '@docusaurus/babel': 3.10.1(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/cssnano-preset': 3.10.1 + '@docusaurus/logger': 3.10.1 + '@docusaurus/types': 3.10.1(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils': 3.10.1(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + babel-loader: 9.2.1(@babel/core@7.29.0)(webpack@5.107.0(postcss@8.5.15)) + clean-css: 5.3.3 + copy-webpack-plugin: 11.0.0(webpack@5.107.0(postcss@8.5.15)) + css-loader: 6.11.0(webpack@5.107.0(postcss@8.5.15)) + css-minimizer-webpack-plugin: 5.0.1(clean-css@5.3.3)(webpack@5.107.0(postcss@8.5.15)) + cssnano: 6.1.2(postcss@8.5.15) + file-loader: 6.2.0(webpack@5.107.0(postcss@8.5.15)) + html-minifier-terser: 7.2.0 + mini-css-extract-plugin: 2.10.2(webpack@5.107.0(postcss@8.5.15)) + null-loader: 4.0.1(webpack@5.107.0(postcss@8.5.15)) + postcss: 8.5.15 + postcss-loader: 7.3.4(postcss@8.5.15)(typescript@6.0.3)(webpack@5.107.0(postcss@8.5.15)) + postcss-preset-env: 10.6.1(postcss@8.5.15) + terser-webpack-plugin: 5.6.0(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(webpack@5.107.0(postcss@8.5.15)) + tslib: 2.8.1 + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.107.0(postcss@8.5.15)))(webpack@5.107.0(postcss@8.5.15)) + webpack: 5.107.0(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15) + webpackbar: 7.0.0(webpack@5.107.0(postcss@8.5.15)) + transitivePeerDependencies: + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - csso - esbuild - lightningcss @@ -14182,97 +14330,103 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/core@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3)': + '@docusaurus/core@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)': dependencies: - '@docusaurus/babel': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/bundler': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/logger': 3.10.0 - '@docusaurus/mdx-loader': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-common': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-validation': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@mdx-js/react': 3.1.1(@types/react@19.2.14)(react@19.2.5) + '@docusaurus/babel': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/bundler': 3.10.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/logger': 3.10.1 + '@docusaurus/mdx-loader': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-common': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-validation': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@mdx-js/react': 3.1.1(@types/react@19.2.15)(react@19.2.6) boxen: 6.2.1 chalk: 4.1.2 chokidar: 3.6.0 cli-table3: 0.6.5 combine-promises: 1.2.0 commander: 5.1.0 - core-js: 3.47.0 + core-js: 3.49.0 detect-port: 1.6.1 escape-html: 1.0.3 eta: 2.2.0 eval: 0.1.8 execa: 5.1.1 - fs-extra: 11.3.4 + fs-extra: 11.3.5 html-tags: 3.3.1 - html-webpack-plugin: 5.6.5(webpack@5.106.2) + html-webpack-plugin: 5.6.7(webpack@5.107.0(postcss@8.5.15)) leven: 3.1.0 lodash: 4.18.1 open: 8.4.2 p-map: 4.0.0 prompts: 2.4.2 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)' - react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.2.5)' - react-loadable-ssr-addon-v5-slorber: 1.0.3(@docusaurus/react-loadable@6.0.0(react@19.2.5))(webpack@5.106.2) - react-router: 5.3.4(react@19.2.5) - react-router-config: 5.1.1(react-router@5.3.4(react@19.2.5))(react@19.2.5) - react-router-dom: 5.3.4(react@19.2.5) - semver: 7.7.4 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)' + react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.2.6)' + react-loadable-ssr-addon-v5-slorber: 1.0.3(@docusaurus/react-loadable@6.0.0(react@19.2.6))(webpack@5.107.0(postcss@8.5.15)) + react-router: 5.3.4(react@19.2.6) + react-router-config: 5.1.1(react-router@5.3.4(react@19.2.6))(react@19.2.6) + react-router-dom: 5.3.4(react@19.2.6) + semver: 7.8.0 serve-handler: 6.1.7 tinypool: 1.1.1 tslib: 2.8.1 update-notifier: 6.0.2 - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) webpack-bundle-analyzer: 4.10.2 - webpack-dev-server: 5.2.2(tslib@2.8.1)(webpack@5.106.2) + webpack-dev-server: 5.2.4(tslib@2.8.1)(webpack@5.107.0(postcss@8.5.15)) webpack-merge: 6.0.1 transitivePeerDependencies: + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss + - postcss - supports-color - typescript - uglify-js - utf-8-validate - webpack-cli - '@docusaurus/cssnano-preset@3.10.0': + '@docusaurus/cssnano-preset@3.10.1': dependencies: - cssnano-preset-advanced: 6.1.2(postcss@8.5.12) - postcss: 8.5.12 - postcss-sort-media-queries: 5.2.0(postcss@8.5.12) + cssnano-preset-advanced: 6.1.2(postcss@8.5.15) + postcss: 8.5.15 + postcss-sort-media-queries: 5.2.0(postcss@8.5.15) tslib: 2.8.1 - '@docusaurus/logger@3.10.0': + '@docusaurus/logger@3.10.1': dependencies: chalk: 4.1.2 tslib: 2.8.1 - '@docusaurus/mdx-loader@3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@docusaurus/mdx-loader@3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@docusaurus/logger': 3.10.0 - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-validation': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/logger': 3.10.1 + '@docusaurus/utils': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-validation': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@mdx-js/mdx': 3.1.1 '@slorber/remark-comment': 1.0.0 escape-html: 1.0.3 estree-util-value-to-estree: 3.5.0 - file-loader: 6.2.0(webpack@5.106.2) - fs-extra: 11.3.4 + file-loader: 6.2.0(webpack@5.107.0(postcss@8.5.15)) + fs-extra: 11.3.5 image-size: 2.0.2 mdast-util-mdx: 3.0.0 mdast-util-to-string: 4.0.0 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) rehype-raw: 7.0.0 remark-directive: 3.0.1 remark-emoji: 4.0.1 @@ -14281,166 +14435,208 @@ snapshots: stringify-object: 3.3.0 tslib: 2.8.1 unified: 11.0.5 - unist-util-visit: 5.0.0 - url-loader: 4.1.1(file-loader@6.2.0(webpack@5.106.2))(webpack@5.106.2) + unist-util-visit: 5.1.0 + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.107.0(postcss@8.5.15)))(webpack@5.107.0(postcss@8.5.15)) vfile: 6.0.3 - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) transitivePeerDependencies: + - '@minify-html/node' - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso - esbuild + - html-minifier-terser + - lightningcss + - postcss - supports-color - uglify-js - webpack-cli - '@docusaurus/module-type-aliases@3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@docusaurus/module-type-aliases@3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@types/history': 4.7.11 - '@types/react': 19.2.14 + '@types/react': 19.2.15 '@types/react-router-config': 5.0.11 '@types/react-router-dom': 5.3.3 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)' - react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.2.5)' + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)' + react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.2.6)' transitivePeerDependencies: + - '@minify-html/node' - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso - esbuild + - html-minifier-terser + - lightningcss + - postcss - supports-color - uglify-js - webpack-cli - '@docusaurus/plugin-content-blog@3.10.0(@docusaurus/plugin-content-docs@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3)': + '@docusaurus/plugin-content-blog@3.10.1(@docusaurus/plugin-content-docs@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/logger': 3.10.0 - '@docusaurus/mdx-loader': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/plugin-content-docs': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/theme-common': 3.10.0(@docusaurus/plugin-content-docs@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-common': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-validation': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/logger': 3.10.1 + '@docusaurus/mdx-loader': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/plugin-content-docs': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/theme-common': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-common': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-validation': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) cheerio: 1.0.0-rc.12 combine-promises: 1.2.0 feed: 4.2.2 - fs-extra: 11.3.4 + fs-extra: 11.3.5 lodash: 4.18.1 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) schema-dts: 1.1.5 srcset: 4.0.0 tslib: 2.8.1 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 utility-types: 3.11.0 - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss + - postcss - supports-color - typescript - uglify-js - utf-8-validate - webpack-cli - '@docusaurus/plugin-content-docs@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3)': + '@docusaurus/plugin-content-docs@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/logger': 3.10.0 - '@docusaurus/mdx-loader': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/module-type-aliases': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/theme-common': 3.10.0(@docusaurus/plugin-content-docs@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-common': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-validation': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/logger': 3.10.1 + '@docusaurus/mdx-loader': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/module-type-aliases': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/theme-common': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-common': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-validation': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@types/react-router-config': 5.0.11 combine-promises: 1.2.0 - fs-extra: 11.3.4 + fs-extra: 11.3.5 js-yaml: 4.1.1 lodash: 4.18.1 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) schema-dts: 1.1.5 tslib: 2.8.1 utility-types: 3.11.0 - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss + - postcss - supports-color - typescript - uglify-js - utf-8-validate - webpack-cli - '@docusaurus/plugin-content-pages@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3)': + '@docusaurus/plugin-content-pages@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/mdx-loader': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-validation': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - fs-extra: 11.3.4 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/mdx-loader': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-validation': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + fs-extra: 11.3.5 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) tslib: 2.8.1 - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss + - postcss - supports-color - typescript - uglify-js - utf-8-validate - webpack-cli - '@docusaurus/plugin-css-cascade-layers@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3)': + '@docusaurus/plugin-css-cascade-layers@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-validation': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-validation': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) tslib: 2.8.1 transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss + - postcss - react - react-dom - supports-color @@ -14449,207 +14645,249 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-debug@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3)': + '@docusaurus/plugin-debug@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - fs-extra: 11.3.4 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - react-json-view-lite: 2.5.0(react@19.2.5) + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + fs-extra: 11.3.5 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-json-view-lite: 2.5.0(react@19.2.6) tslib: 2.8.1 transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss + - postcss - supports-color - typescript - uglify-js - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-analytics@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3)': + '@docusaurus/plugin-google-analytics@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-validation': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-validation': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) tslib: 2.8.1 transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss + - postcss - supports-color - typescript - uglify-js - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-gtag@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3)': + '@docusaurus/plugin-google-gtag@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-validation': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-validation': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@types/gtag.js': 0.0.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) tslib: 2.8.1 transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss + - postcss - supports-color - typescript - uglify-js - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-tag-manager@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3)': + '@docusaurus/plugin-google-tag-manager@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-validation': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-validation': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) tslib: 2.8.1 transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss + - postcss - supports-color - typescript - uglify-js - utf-8-validate - webpack-cli - '@docusaurus/plugin-sitemap@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3)': + '@docusaurus/plugin-sitemap@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/logger': 3.10.0 - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-common': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-validation': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - fs-extra: 11.3.4 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - sitemap: 7.1.2 + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/logger': 3.10.1 + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-common': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-validation': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + fs-extra: 11.3.5 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + sitemap: 7.1.3 tslib: 2.8.1 transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss + - postcss - supports-color - typescript - uglify-js - utf-8-validate - webpack-cli - '@docusaurus/plugin-svgr@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3)': + '@docusaurus/plugin-svgr@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-validation': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-validation': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@svgr/core': 8.1.0(typescript@6.0.3) '@svgr/webpack': 8.1.0(typescript@6.0.3) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) tslib: 2.8.1 - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss + - postcss - supports-color - typescript - uglify-js - utf-8-validate - webpack-cli - '@docusaurus/preset-classic@3.10.0(@algolia/client-search@5.46.0)(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(search-insights@2.17.3)(typescript@6.0.3)': + '@docusaurus/preset-classic@3.10.1(@algolia/client-search@5.52.1)(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(@types/react@19.2.15)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(search-insights@2.17.3)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/plugin-content-blog': 3.10.0(@docusaurus/plugin-content-docs@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/plugin-content-docs': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/plugin-content-pages': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/plugin-css-cascade-layers': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/plugin-debug': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/plugin-google-analytics': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/plugin-google-gtag': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/plugin-google-tag-manager': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/plugin-sitemap': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/plugin-svgr': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/theme-classic': 3.10.0(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/theme-common': 3.10.0(@docusaurus/plugin-content-docs@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/theme-search-algolia': 3.10.0(@algolia/client-search@5.46.0)(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(search-insights@2.17.3)(typescript@6.0.3) - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/plugin-content-blog': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/plugin-content-docs': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/plugin-content-pages': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/plugin-css-cascade-layers': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/plugin-debug': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/plugin-google-analytics': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/plugin-google-gtag': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/plugin-google-tag-manager': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/plugin-sitemap': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/plugin-svgr': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/theme-classic': 3.10.1(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/theme-common': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/theme-search-algolia': 3.10.1(@algolia/client-search@5.52.1)(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(@types/react@19.2.15)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(search-insights@2.17.3)(typescript@6.0.3) + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) transitivePeerDependencies: - '@algolia/client-search' - '@docusaurus/faster' - '@mdx-js/react' + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - '@types/react' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss + - postcss - search-insights - supports-color - typescript @@ -14657,52 +14895,57 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/react-loadable@6.0.0(react@19.2.5)': + '@docusaurus/react-loadable@6.0.0(react@19.2.6)': dependencies: - '@types/react': 19.2.14 - react: 19.2.5 + '@types/react': 19.2.15 + react: 19.2.6 - '@docusaurus/theme-classic@3.10.0(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3)': + '@docusaurus/theme-classic@3.10.1(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/logger': 3.10.0 - '@docusaurus/mdx-loader': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/module-type-aliases': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/plugin-content-blog': 3.10.0(@docusaurus/plugin-content-docs@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/plugin-content-docs': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/plugin-content-pages': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/theme-common': 3.10.0(@docusaurus/plugin-content-docs@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/theme-translations': 3.10.0 - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-common': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-validation': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@mdx-js/react': 3.1.1(@types/react@19.2.14)(react@19.2.5) + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/logger': 3.10.1 + '@docusaurus/mdx-loader': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/module-type-aliases': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/plugin-content-blog': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/plugin-content-docs': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/plugin-content-pages': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/theme-common': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/theme-translations': 3.10.1 + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-common': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-validation': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@mdx-js/react': 3.1.1(@types/react@19.2.15)(react@19.2.6) clsx: 2.1.1 copy-text-to-clipboard: 3.2.2 infima: 0.2.0-alpha.45 lodash: 4.18.1 nprogress: 0.2.0 - postcss: 8.5.12 - prism-react-renderer: 2.4.1(react@19.2.5) + postcss: 8.5.15 + prism-react-renderer: 2.4.1(react@19.2.6) prismjs: 1.30.0 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - react-router-dom: 5.3.4(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-router-dom: 5.3.4(react@19.2.6) rtlcss: 4.3.0 tslib: 2.8.1 utility-types: 3.11.0 transitivePeerDependencies: - '@docusaurus/faster' + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - '@types/react' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss - supports-color - typescript @@ -14710,95 +14953,116 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/theme-common@3.10.0(@docusaurus/plugin-content-docs@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@docusaurus/theme-common@3.10.1(@docusaurus/plugin-content-docs@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@docusaurus/mdx-loader': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/module-type-aliases': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/plugin-content-docs': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-common': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/mdx-loader': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/module-type-aliases': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/plugin-content-docs': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/utils': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-common': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@types/history': 4.7.11 - '@types/react': 19.2.14 + '@types/react': 19.2.15 '@types/react-router-config': 5.0.11 clsx: 2.1.1 parse-numeric-range: 1.3.0 - prism-react-renderer: 2.4.1(react@19.2.5) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + prism-react-renderer: 2.4.1(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) tslib: 2.8.1 utility-types: 3.11.0 transitivePeerDependencies: + - '@minify-html/node' - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso - esbuild + - html-minifier-terser + - lightningcss + - postcss - supports-color - uglify-js - webpack-cli - '@docusaurus/theme-mermaid@3.10.0(@docusaurus/plugin-content-docs@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3)': + '@docusaurus/theme-mermaid@3.10.1(@docusaurus/plugin-content-docs@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/module-type-aliases': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/theme-common': 3.10.0(@docusaurus/plugin-content-docs@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-validation': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - mermaid: 11.12.2 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/module-type-aliases': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/theme-common': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-validation': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + mermaid: 11.15.0 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) tslib: 2.8.1 transitivePeerDependencies: - '@docusaurus/faster' - '@docusaurus/plugin-content-docs' - '@mdx-js/react' + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss + - postcss - supports-color - typescript - uglify-js - utf-8-validate - webpack-cli - '@docusaurus/theme-search-algolia@3.10.0(@algolia/client-search@5.46.0)(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(search-insights@2.17.3)(typescript@6.0.3)': + '@docusaurus/theme-search-algolia@3.10.1(@algolia/client-search@5.52.1)(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(@types/react@19.2.15)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(search-insights@2.17.3)(typescript@6.0.3)': dependencies: - '@algolia/autocomplete-core': 1.19.2(@algolia/client-search@5.46.0)(algoliasearch@5.46.0)(search-insights@2.17.3) - '@docsearch/react': 4.3.2(@algolia/client-search@5.46.0)(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(search-insights@2.17.3) - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/logger': 3.10.0 - '@docusaurus/plugin-content-docs': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/theme-common': 3.10.0(@docusaurus/plugin-content-docs@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/theme-translations': 3.10.0 - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-validation': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - algoliasearch: 5.46.0 - algoliasearch-helper: 3.26.1(algoliasearch@5.46.0) + '@algolia/autocomplete-core': 1.19.8(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)(search-insights@2.17.3) + '@docsearch/react': 4.6.3(@algolia/client-search@5.52.1)(@types/react@19.2.15)(algoliasearch@5.52.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(search-insights@2.17.3) + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/logger': 3.10.1 + '@docusaurus/plugin-content-docs': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/theme-common': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/theme-translations': 3.10.1 + '@docusaurus/utils': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-validation': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + algoliasearch: 5.52.1 + algoliasearch-helper: 3.29.1(algoliasearch@5.52.1) clsx: 2.1.1 eta: 2.2.0 - fs-extra: 11.3.4 + fs-extra: 11.3.5 lodash: 4.18.1 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) tslib: 2.8.1 utility-types: 3.11.0 transitivePeerDependencies: - '@algolia/client-search' - '@docusaurus/faster' - '@mdx-js/react' + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - '@types/react' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss + - postcss - search-insights - supports-color - typescript @@ -14806,75 +15070,154 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/theme-translations@3.10.0': + '@docusaurus/theme-translations@3.10.1': dependencies: - fs-extra: 11.3.4 + fs-extra: 11.3.5 tslib: 2.8.1 - '@docusaurus/tsconfig@3.10.0': {} + '@docusaurus/tsconfig@3.10.1': {} - '@docusaurus/types@3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@docusaurus/types@3.10.1(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@mdx-js/mdx': 3.1.1 '@types/history': 4.7.11 '@types/mdast': 4.0.4 - '@types/react': 19.2.14 + '@types/react': 19.2.15 commander: 5.1.0 joi: 17.13.3 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)' + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)' utility-types: 3.11.0 - webpack: 5.106.2 + webpack: 5.107.0(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15) webpack-merge: 5.10.0 transitivePeerDependencies: + - '@minify-html/node' - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso - esbuild + - html-minifier-terser + - lightningcss + - postcss - supports-color - uglify-js - webpack-cli - '@docusaurus/utils-common@3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@docusaurus/types@3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@mdx-js/mdx': 3.1.1 + '@types/history': 4.7.11 + '@types/mdast': 4.0.4 + '@types/react': 19.2.15 + commander: 5.1.0 + joi: 17.13.3 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)' + utility-types: 3.11.0 + webpack: 5.107.0(postcss@8.5.15) + webpack-merge: 5.10.0 + transitivePeerDependencies: + - '@minify-html/node' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - supports-color + - uglify-js + - webpack-cli + + '@docusaurus/utils-common@3.10.1(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@docusaurus/types': 3.10.1(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) tslib: 2.8.1 transitivePeerDependencies: + - '@minify-html/node' - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso - esbuild + - html-minifier-terser + - lightningcss + - postcss - react - react-dom - supports-color - uglify-js - webpack-cli - '@docusaurus/utils-validation@3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@docusaurus/utils-common@3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@docusaurus/logger': 3.10.0 - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-common': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - fs-extra: 11.3.4 + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + tslib: 2.8.1 + transitivePeerDependencies: + - '@minify-html/node' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - react + - react-dom + - supports-color + - uglify-js + - webpack-cli + + '@docusaurus/utils-validation@3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@docusaurus/logger': 3.10.1 + '@docusaurus/utils': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-common': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + fs-extra: 11.3.5 joi: 17.13.3 js-yaml: 4.1.1 lodash: 4.18.1 tslib: 2.8.1 transitivePeerDependencies: + - '@minify-html/node' - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso - esbuild + - html-minifier-terser + - lightningcss + - postcss - react - react-dom - supports-color - uglify-js - webpack-cli - '@docusaurus/utils@3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@docusaurus/utils@3.10.1(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@docusaurus/logger': 3.10.0 - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-common': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/logger': 3.10.1 + '@docusaurus/types': 3.10.1(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-common': 3.10.1(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) escape-string-regexp: 4.0.0 execa: 5.1.1 - file-loader: 6.2.0(webpack@5.106.2) - fs-extra: 11.3.4 + file-loader: 6.2.0(webpack@5.107.0(postcss@8.5.15)) + fs-extra: 11.3.5 github-slugger: 1.5.0 globby: 11.1.0 gray-matter: 4.0.3 @@ -14886,12 +15229,62 @@ snapshots: prompts: 2.4.2 resolve-pathname: 3.0.0 tslib: 2.8.1 - url-loader: 4.1.1(file-loader@6.2.0(webpack@5.106.2))(webpack@5.106.2) + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.107.0(postcss@8.5.15)))(webpack@5.107.0(postcss@8.5.15)) utility-types: 3.11.0 - webpack: 5.106.2 + webpack: 5.107.0(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15) transitivePeerDependencies: + - '@minify-html/node' - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso - esbuild + - html-minifier-terser + - lightningcss + - postcss + - react + - react-dom + - supports-color + - uglify-js + - webpack-cli + + '@docusaurus/utils@3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@docusaurus/logger': 3.10.1 + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-common': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + escape-string-regexp: 4.0.0 + execa: 5.1.1 + file-loader: 6.2.0(webpack@5.107.0(postcss@8.5.15)) + fs-extra: 11.3.5 + github-slugger: 1.5.0 + globby: 11.1.0 + gray-matter: 4.0.3 + jiti: 1.21.7 + js-yaml: 4.1.1 + lodash: 4.18.1 + micromatch: 4.0.8 + p-queue: 6.6.2 + prompts: 2.4.2 + resolve-pathname: 3.0.0 + tslib: 2.8.1 + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.107.0(postcss@8.5.15)))(webpack@5.107.0(postcss@8.5.15)) + utility-types: 3.11.0 + webpack: 5.107.0(postcss@8.5.15) + transitivePeerDependencies: + - '@minify-html/node' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso + - esbuild + - html-minifier-terser + - lightningcss + - postcss - react - react-dom - supports-color @@ -14920,6 +15313,9 @@ snapshots: '@esbuild/aix-ppc64@0.27.4': optional: true + '@esbuild/aix-ppc64@0.27.7': + optional: true + '@esbuild/aix-ppc64@0.28.0': optional: true @@ -14929,6 +15325,9 @@ snapshots: '@esbuild/android-arm64@0.27.4': optional: true + '@esbuild/android-arm64@0.27.7': + optional: true + '@esbuild/android-arm64@0.28.0': optional: true @@ -14938,6 +15337,9 @@ snapshots: '@esbuild/android-arm@0.27.4': optional: true + '@esbuild/android-arm@0.27.7': + optional: true + '@esbuild/android-arm@0.28.0': optional: true @@ -14947,6 +15349,9 @@ snapshots: '@esbuild/android-x64@0.27.4': optional: true + '@esbuild/android-x64@0.27.7': + optional: true + '@esbuild/android-x64@0.28.0': optional: true @@ -14956,6 +15361,9 @@ snapshots: '@esbuild/darwin-arm64@0.27.4': optional: true + '@esbuild/darwin-arm64@0.27.7': + optional: true + '@esbuild/darwin-arm64@0.28.0': optional: true @@ -14965,6 +15373,9 @@ snapshots: '@esbuild/darwin-x64@0.27.4': optional: true + '@esbuild/darwin-x64@0.27.7': + optional: true + '@esbuild/darwin-x64@0.28.0': optional: true @@ -14974,6 +15385,9 @@ snapshots: '@esbuild/freebsd-arm64@0.27.4': optional: true + '@esbuild/freebsd-arm64@0.27.7': + optional: true + '@esbuild/freebsd-arm64@0.28.0': optional: true @@ -14983,6 +15397,9 @@ snapshots: '@esbuild/freebsd-x64@0.27.4': optional: true + '@esbuild/freebsd-x64@0.27.7': + optional: true + '@esbuild/freebsd-x64@0.28.0': optional: true @@ -14992,6 +15409,9 @@ snapshots: '@esbuild/linux-arm64@0.27.4': optional: true + '@esbuild/linux-arm64@0.27.7': + optional: true + '@esbuild/linux-arm64@0.28.0': optional: true @@ -15001,6 +15421,9 @@ snapshots: '@esbuild/linux-arm@0.27.4': optional: true + '@esbuild/linux-arm@0.27.7': + optional: true + '@esbuild/linux-arm@0.28.0': optional: true @@ -15010,6 +15433,9 @@ snapshots: '@esbuild/linux-ia32@0.27.4': optional: true + '@esbuild/linux-ia32@0.27.7': + optional: true + '@esbuild/linux-ia32@0.28.0': optional: true @@ -15019,6 +15445,9 @@ snapshots: '@esbuild/linux-loong64@0.27.4': optional: true + '@esbuild/linux-loong64@0.27.7': + optional: true + '@esbuild/linux-loong64@0.28.0': optional: true @@ -15028,6 +15457,9 @@ snapshots: '@esbuild/linux-mips64el@0.27.4': optional: true + '@esbuild/linux-mips64el@0.27.7': + optional: true + '@esbuild/linux-mips64el@0.28.0': optional: true @@ -15037,6 +15469,9 @@ snapshots: '@esbuild/linux-ppc64@0.27.4': optional: true + '@esbuild/linux-ppc64@0.27.7': + optional: true + '@esbuild/linux-ppc64@0.28.0': optional: true @@ -15046,6 +15481,9 @@ snapshots: '@esbuild/linux-riscv64@0.27.4': optional: true + '@esbuild/linux-riscv64@0.27.7': + optional: true + '@esbuild/linux-riscv64@0.28.0': optional: true @@ -15055,6 +15493,9 @@ snapshots: '@esbuild/linux-s390x@0.27.4': optional: true + '@esbuild/linux-s390x@0.27.7': + optional: true + '@esbuild/linux-s390x@0.28.0': optional: true @@ -15064,12 +15505,18 @@ snapshots: '@esbuild/linux-x64@0.27.4': optional: true + '@esbuild/linux-x64@0.27.7': + optional: true + '@esbuild/linux-x64@0.28.0': optional: true '@esbuild/netbsd-arm64@0.27.4': optional: true + '@esbuild/netbsd-arm64@0.27.7': + optional: true + '@esbuild/netbsd-arm64@0.28.0': optional: true @@ -15079,12 +15526,18 @@ snapshots: '@esbuild/netbsd-x64@0.27.4': optional: true + '@esbuild/netbsd-x64@0.27.7': + optional: true + '@esbuild/netbsd-x64@0.28.0': optional: true '@esbuild/openbsd-arm64@0.27.4': optional: true + '@esbuild/openbsd-arm64@0.27.7': + optional: true + '@esbuild/openbsd-arm64@0.28.0': optional: true @@ -15094,12 +15547,18 @@ snapshots: '@esbuild/openbsd-x64@0.27.4': optional: true + '@esbuild/openbsd-x64@0.27.7': + optional: true + '@esbuild/openbsd-x64@0.28.0': optional: true '@esbuild/openharmony-arm64@0.27.4': optional: true + '@esbuild/openharmony-arm64@0.27.7': + optional: true + '@esbuild/openharmony-arm64@0.28.0': optional: true @@ -15109,6 +15568,9 @@ snapshots: '@esbuild/sunos-x64@0.27.4': optional: true + '@esbuild/sunos-x64@0.27.7': + optional: true + '@esbuild/sunos-x64@0.28.0': optional: true @@ -15118,6 +15580,9 @@ snapshots: '@esbuild/win32-arm64@0.27.4': optional: true + '@esbuild/win32-arm64@0.27.7': + optional: true + '@esbuild/win32-arm64@0.28.0': optional: true @@ -15127,6 +15592,9 @@ snapshots: '@esbuild/win32-ia32@0.27.4': optional: true + '@esbuild/win32-ia32@0.27.7': + optional: true + '@esbuild/win32-ia32@0.28.0': optional: true @@ -15136,12 +15604,15 @@ snapshots: '@esbuild/win32-x64@0.27.4': optional: true + '@esbuild/win32-x64@0.27.7': + optional: true + '@esbuild/win32-x64@0.28.0': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@10.2.1(jiti@2.6.1))': + '@eslint-community/eslint-utils@4.9.1(eslint@10.4.0(jiti@2.7.0))': dependencies: - eslint: 10.2.1(jiti@2.6.1) + eslint: 10.4.0(jiti@2.7.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -15154,7 +15625,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.5.5': + '@eslint/config-helpers@0.6.0': dependencies: '@eslint/core': 1.2.1 @@ -15162,14 +15633,14 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/css-tree@4.0.2': + '@eslint/css-tree@4.0.3': dependencies: - mdn-data: 2.27.1 + mdn-data: 2.28.0 source-map-js: 1.2.1 - '@eslint/js@10.0.1(eslint@10.2.1(jiti@2.6.1))': + '@eslint/js@10.0.1(eslint@10.4.0(jiti@2.7.0))': optionalDependencies: - eslint: 10.2.1(jiti@2.6.1) + eslint: 10.4.0(jiti@2.7.0) '@eslint/object-schema@3.0.5': {} @@ -15213,7 +15684,7 @@ snapshots: dependencies: tslib: 2.8.1 - '@formatjs/fast-memoize@3.1.2': {} + '@formatjs/fast-memoize@3.1.5': {} '@formatjs/icu-messageformat-parser@2.11.4': dependencies: @@ -15221,16 +15692,16 @@ snapshots: '@formatjs/icu-skeleton-parser': 1.8.16 tslib: 2.8.1 - '@formatjs/icu-messageformat-parser@3.5.4': + '@formatjs/icu-messageformat-parser@3.5.9': dependencies: - '@formatjs/icu-skeleton-parser': 2.1.4 + '@formatjs/icu-skeleton-parser': 2.1.9 '@formatjs/icu-skeleton-parser@1.8.16': dependencies: '@formatjs/ecma402-abstract': 2.3.6 tslib: 2.8.1 - '@formatjs/icu-skeleton-parser@2.1.4': {} + '@formatjs/icu-skeleton-parser@2.1.9': {} '@formatjs/intl-localematcher@0.6.2': dependencies: @@ -15246,29 +15717,29 @@ snapshots: dependencies: '@fortawesome/fontawesome-common-types': 7.2.0 - '@golevelup/nestjs-discovery@5.0.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)': + '@golevelup/nestjs-discovery@5.0.0(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) lodash: 4.18.1 '@grpc/grpc-js@1.14.3': dependencies: - '@grpc/proto-loader': 0.8.0 + '@grpc/proto-loader': 0.8.1 '@js-sdsl/ordered-map': 4.4.2 '@grpc/proto-loader@0.7.15': dependencies: lodash.camelcase: 4.3.0 long: 5.3.2 - protobufjs: 7.5.5 + protobufjs: 7.6.0 yargs: 17.7.2 - '@grpc/proto-loader@0.8.0': + '@grpc/proto-loader@0.8.1': dependencies: lodash.camelcase: 4.3.0 long: 5.3.2 - protobufjs: 7.5.5 + protobufjs: 7.6.0 yargs: 17.7.2 '@hapi/hoek@9.3.0': {} @@ -15295,13 +15766,13 @@ snapshots: '@iconify/types@2.0.0': {} - '@iconify/utils@3.1.0': + '@iconify/utils@3.1.3': dependencies: '@antfu/install-pkg': 1.1.0 '@iconify/types': 2.0.0 - mlly: 1.8.0 + import-meta-resolve: 4.2.0 - '@img/colour@1.0.0': {} + '@img/colour@1.1.0': {} '@img/sharp-darwin-arm64@0.34.5': optionalDependencies: @@ -15405,162 +15876,162 @@ snapshots: graph-data-structure: 4.5.0 kysely: 0.28.17 kysely-postgres-js: 3.0.0(kysely@0.28.17)(postgres@3.4.9) - pg-connection-string: 2.12.0 + pg-connection-string: 2.13.0 postgres: 3.4.9 - '@immich/ui@0.77.3(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)': + '@immich/ui@0.77.3(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))': dependencies: '@internationalized/date': 3.12.1 '@mdi/js': 7.4.47 - '@sveltejs/kit': 2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - bits-ui: 2.18.0(@internationalized/date@3.12.1)(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2) + '@sveltejs/kit': 2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) + bits-ui: 2.18.1(@internationalized/date@3.12.1)(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4)) luxon: 3.7.2 - simple-icons: 16.17.0 - svelte: 5.55.2 + simple-icons: 16.20.0 + svelte: 5.55.8(@typescript-eslint/types@8.59.4) svelte-highlight: 7.9.0 - tailwind-merge: 3.5.0 - tailwind-variants: 3.2.2(tailwind-merge@3.5.0)(tailwindcss@4.2.4) - tailwindcss: 4.2.4 + tailwind-merge: 3.6.0 + tailwind-variants: 3.2.2(tailwind-merge@3.6.0)(tailwindcss@4.3.0) + tailwindcss: 4.3.0 '@inquirer/ansi@1.0.2': {} - '@inquirer/checkbox@4.3.2(@types/node@24.12.2)': + '@inquirer/checkbox@4.3.2(@types/node@24.12.4)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@24.12.2) + '@inquirer/core': 10.3.2(@types/node@24.12.4) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@24.12.2) + '@inquirer/type': 3.0.10(@types/node@24.12.4) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@inquirer/confirm@5.1.21(@types/node@24.12.2)': + '@inquirer/confirm@5.1.21(@types/node@24.12.4)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.12.2) - '@inquirer/type': 3.0.10(@types/node@24.12.2) + '@inquirer/core': 10.3.2(@types/node@24.12.4) + '@inquirer/type': 3.0.10(@types/node@24.12.4) optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@inquirer/core@10.3.2(@types/node@24.12.2)': + '@inquirer/core@10.3.2(@types/node@24.12.4)': dependencies: '@inquirer/ansi': 1.0.2 '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@24.12.2) + '@inquirer/type': 3.0.10(@types/node@24.12.4) cli-width: 4.1.0 mute-stream: 2.0.0 signal-exit: 4.1.0 wrap-ansi: 6.2.0 yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@inquirer/editor@4.2.23(@types/node@24.12.2)': + '@inquirer/editor@4.2.23(@types/node@24.12.4)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.12.2) - '@inquirer/external-editor': 1.0.3(@types/node@24.12.2) - '@inquirer/type': 3.0.10(@types/node@24.12.2) + '@inquirer/core': 10.3.2(@types/node@24.12.4) + '@inquirer/external-editor': 1.0.3(@types/node@24.12.4) + '@inquirer/type': 3.0.10(@types/node@24.12.4) optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@inquirer/expand@4.0.23(@types/node@24.12.2)': + '@inquirer/expand@4.0.23(@types/node@24.12.4)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.12.2) - '@inquirer/type': 3.0.10(@types/node@24.12.2) + '@inquirer/core': 10.3.2(@types/node@24.12.4) + '@inquirer/type': 3.0.10(@types/node@24.12.4) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@inquirer/external-editor@1.0.3(@types/node@24.12.2)': + '@inquirer/external-editor@1.0.3(@types/node@24.12.4)': dependencies: chardet: 2.1.1 iconv-lite: 0.7.2 optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@inquirer/figures@1.0.15': {} - '@inquirer/input@4.3.1(@types/node@24.12.2)': + '@inquirer/input@4.3.1(@types/node@24.12.4)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.12.2) - '@inquirer/type': 3.0.10(@types/node@24.12.2) + '@inquirer/core': 10.3.2(@types/node@24.12.4) + '@inquirer/type': 3.0.10(@types/node@24.12.4) optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@inquirer/number@3.0.23(@types/node@24.12.2)': + '@inquirer/number@3.0.23(@types/node@24.12.4)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.12.2) - '@inquirer/type': 3.0.10(@types/node@24.12.2) + '@inquirer/core': 10.3.2(@types/node@24.12.4) + '@inquirer/type': 3.0.10(@types/node@24.12.4) optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@inquirer/password@4.0.23(@types/node@24.12.2)': + '@inquirer/password@4.0.23(@types/node@24.12.4)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@24.12.2) - '@inquirer/type': 3.0.10(@types/node@24.12.2) + '@inquirer/core': 10.3.2(@types/node@24.12.4) + '@inquirer/type': 3.0.10(@types/node@24.12.4) optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@inquirer/prompts@7.10.1(@types/node@24.12.2)': + '@inquirer/prompts@7.10.1(@types/node@24.12.4)': dependencies: - '@inquirer/checkbox': 4.3.2(@types/node@24.12.2) - '@inquirer/confirm': 5.1.21(@types/node@24.12.2) - '@inquirer/editor': 4.2.23(@types/node@24.12.2) - '@inquirer/expand': 4.0.23(@types/node@24.12.2) - '@inquirer/input': 4.3.1(@types/node@24.12.2) - '@inquirer/number': 3.0.23(@types/node@24.12.2) - '@inquirer/password': 4.0.23(@types/node@24.12.2) - '@inquirer/rawlist': 4.1.11(@types/node@24.12.2) - '@inquirer/search': 3.2.2(@types/node@24.12.2) - '@inquirer/select': 4.4.2(@types/node@24.12.2) + '@inquirer/checkbox': 4.3.2(@types/node@24.12.4) + '@inquirer/confirm': 5.1.21(@types/node@24.12.4) + '@inquirer/editor': 4.2.23(@types/node@24.12.4) + '@inquirer/expand': 4.0.23(@types/node@24.12.4) + '@inquirer/input': 4.3.1(@types/node@24.12.4) + '@inquirer/number': 3.0.23(@types/node@24.12.4) + '@inquirer/password': 4.0.23(@types/node@24.12.4) + '@inquirer/rawlist': 4.1.11(@types/node@24.12.4) + '@inquirer/search': 3.2.2(@types/node@24.12.4) + '@inquirer/select': 4.4.2(@types/node@24.12.4) optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@inquirer/prompts@7.3.2(@types/node@24.12.2)': + '@inquirer/prompts@7.3.2(@types/node@24.12.4)': dependencies: - '@inquirer/checkbox': 4.3.2(@types/node@24.12.2) - '@inquirer/confirm': 5.1.21(@types/node@24.12.2) - '@inquirer/editor': 4.2.23(@types/node@24.12.2) - '@inquirer/expand': 4.0.23(@types/node@24.12.2) - '@inquirer/input': 4.3.1(@types/node@24.12.2) - '@inquirer/number': 3.0.23(@types/node@24.12.2) - '@inquirer/password': 4.0.23(@types/node@24.12.2) - '@inquirer/rawlist': 4.1.11(@types/node@24.12.2) - '@inquirer/search': 3.2.2(@types/node@24.12.2) - '@inquirer/select': 4.4.2(@types/node@24.12.2) + '@inquirer/checkbox': 4.3.2(@types/node@24.12.4) + '@inquirer/confirm': 5.1.21(@types/node@24.12.4) + '@inquirer/editor': 4.2.23(@types/node@24.12.4) + '@inquirer/expand': 4.0.23(@types/node@24.12.4) + '@inquirer/input': 4.3.1(@types/node@24.12.4) + '@inquirer/number': 3.0.23(@types/node@24.12.4) + '@inquirer/password': 4.0.23(@types/node@24.12.4) + '@inquirer/rawlist': 4.1.11(@types/node@24.12.4) + '@inquirer/search': 3.2.2(@types/node@24.12.4) + '@inquirer/select': 4.4.2(@types/node@24.12.4) optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@inquirer/rawlist@4.1.11(@types/node@24.12.2)': + '@inquirer/rawlist@4.1.11(@types/node@24.12.4)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.12.2) - '@inquirer/type': 3.0.10(@types/node@24.12.2) + '@inquirer/core': 10.3.2(@types/node@24.12.4) + '@inquirer/type': 3.0.10(@types/node@24.12.4) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@inquirer/search@3.2.2(@types/node@24.12.2)': + '@inquirer/search@3.2.2(@types/node@24.12.4)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.12.2) + '@inquirer/core': 10.3.2(@types/node@24.12.4) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@24.12.2) + '@inquirer/type': 3.0.10(@types/node@24.12.4) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@inquirer/select@4.4.2(@types/node@24.12.2)': + '@inquirer/select@4.4.2(@types/node@24.12.4)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@24.12.2) + '@inquirer/core': 10.3.2(@types/node@24.12.4) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@24.12.2) + '@inquirer/type': 3.0.10(@types/node@24.12.4) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@inquirer/type@3.0.10(@types/node@24.12.2)': + '@inquirer/type@3.0.10(@types/node@24.12.4)': optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@internationalized/date@3.12.1': dependencies: @@ -15585,14 +16056,14 @@ snapshots: '@jest/schemas@29.6.3': dependencies: - '@sinclair/typebox': 0.27.8 + '@sinclair/typebox': 0.27.10 '@jest/types@29.6.3': dependencies: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/yargs': 17.0.35 chalk: 4.1.2 @@ -15658,7 +16129,7 @@ snapshots: dependencies: '@jsonjoy.com/fs-node-builtins': 4.57.2(tslib@2.8.1) '@jsonjoy.com/fs-node-utils': 4.57.2(tslib@2.8.1) - thingies: 2.5.0(tslib@2.8.1) + thingies: 2.6.0(tslib@2.8.1) tslib: 2.8.1 '@jsonjoy.com/fs-fsa@4.57.2(tslib@2.8.1)': @@ -15666,7 +16137,7 @@ snapshots: '@jsonjoy.com/fs-core': 4.57.2(tslib@2.8.1) '@jsonjoy.com/fs-node-builtins': 4.57.2(tslib@2.8.1) '@jsonjoy.com/fs-node-utils': 4.57.2(tslib@2.8.1) - thingies: 2.5.0(tslib@2.8.1) + thingies: 2.6.0(tslib@2.8.1) tslib: 2.8.1 '@jsonjoy.com/fs-node-builtins@4.57.2(tslib@2.8.1)': @@ -15693,7 +16164,7 @@ snapshots: '@jsonjoy.com/fs-print': 4.57.2(tslib@2.8.1) '@jsonjoy.com/fs-snapshot': 4.57.2(tslib@2.8.1) glob-to-regex.js: 1.2.0(tslib@2.8.1) - thingies: 2.5.0(tslib@2.8.1) + thingies: 2.6.0(tslib@2.8.1) tslib: 2.8.1 '@jsonjoy.com/fs-print@4.57.2(tslib@2.8.1)': @@ -15718,7 +16189,7 @@ snapshots: '@jsonjoy.com/json-pointer': 1.0.2(tslib@2.8.1) '@jsonjoy.com/util': 1.9.0(tslib@2.8.1) hyperdyperid: 1.2.0 - thingies: 2.5.0(tslib@2.8.1) + thingies: 2.6.0(tslib@2.8.1) tree-dump: 1.1.0(tslib@2.8.1) tslib: 2.8.1 @@ -15730,7 +16201,7 @@ snapshots: '@jsonjoy.com/json-pointer': 17.67.0(tslib@2.8.1) '@jsonjoy.com/util': 17.67.0(tslib@2.8.1) hyperdyperid: 1.2.0 - thingies: 2.5.0(tslib@2.8.1) + thingies: 2.6.0(tslib@2.8.1) tree-dump: 1.1.0(tslib@2.8.1) tslib: 2.8.1 @@ -15763,7 +16234,7 @@ snapshots: dependencies: vary: 1.1.2 - '@koa/router@15.4.0(koa@3.2.0)': + '@koa/router@15.5.0(koa@3.2.0)': dependencies: debug: 4.4.3 http-errors: 2.0.1 @@ -15773,11 +16244,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@koddsson/eslint-plugin-tscompat@0.2.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': + '@koddsson/eslint-plugin-tscompat@0.2.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@mdn/browser-compat-data': 6.1.5 - '@typescript-eslint/type-utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/type-utils': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) browserslist: 4.28.2 transitivePeerDependencies: - eslint @@ -15814,16 +16285,16 @@ snapshots: '@mapbox/mapbox-gl-rtl-text@0.4.0': {} - '@mapbox/node-pre-gyp@1.0.11(encoding@0.1.13)': + '@mapbox/node-pre-gyp@1.0.11': dependencies: detect-libc: 2.1.2 https-proxy-agent: 5.0.1 make-dir: 3.1.0 - node-fetch: 2.7.0(encoding@0.1.13) + node-fetch: 2.7.0 nopt: 5.0.0 npmlog: 5.0.1 rimraf: 3.0.2 - semver: 7.7.4 + semver: 7.8.0 tar: 6.2.1 transitivePeerDependencies: - encoding @@ -15831,7 +16302,7 @@ snapshots: '@mapbox/point-geometry@1.1.0': {} - '@mapbox/tiny-sdf@2.1.0': {} + '@mapbox/tiny-sdf@2.2.0': {} '@mapbox/unitbezier@0.0.1': {} @@ -15847,16 +16318,15 @@ snapshots: '@maplibre/geojson-vt@6.1.0': dependencies: - kdbush: 4.0.2 + kdbush: 4.1.0 - '@maplibre/maplibre-gl-style-spec@24.8.1': + '@maplibre/maplibre-gl-style-spec@24.8.5': dependencies: '@mapbox/jsonlint-lines-primitives': 2.0.2 '@mapbox/unitbezier': 0.0.1 json-stringify-pretty-compact: 4.0.0 minimist: 1.2.8 quickselect: 3.0.0 - rw: 1.3.3 tinyqueue: 3.0.0 '@maplibre/mlt@1.1.9': @@ -15887,7 +16357,7 @@ snapshots: '@mdx-js/mdx@3.1.1': dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/estree-jsx': 1.0.5 '@types/hast': 3.0.4 '@types/mdx': 2.0.13 @@ -15910,20 +16380,20 @@ snapshots: unified: 11.0.5 unist-util-position-from-estree: 2.0.0 unist-util-stringify-position: 4.0.0 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 vfile: 6.0.3 transitivePeerDependencies: - supports-color - '@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5)': + '@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6)': dependencies: '@types/mdx': 2.0.13 - '@types/react': 19.2.14 - react: 19.2.5 + '@types/react': 19.2.15 + react: 19.2.6 - '@mermaid-js/parser@0.6.3': + '@mermaid-js/parser@1.1.1': dependencies: - langium: 3.3.1 + '@chevrotain/types': 11.1.2 '@microsoft/tsdoc@0.16.0': {} @@ -15951,53 +16421,62 @@ snapshots: dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.1 + '@tybys/wasm-util': 0.10.2 optional: true - '@nestjs/bull-shared@11.0.4(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)': + '@nestjs/bull-shared@11.0.4(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) tslib: 2.8.1 - '@nestjs/bullmq@11.0.4(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(bullmq@5.76.1)': + '@nestjs/bullmq@11.0.4(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(bullmq@5.76.10)': dependencies: - '@nestjs/bull-shared': 11.0.4(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) - bullmq: 5.76.1 + '@nestjs/bull-shared': 11.0.4(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) + bullmq: 5.76.10 tslib: 2.8.1 - '@nestjs/cli@11.0.21(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@24.12.2)(esbuild@0.28.0)(prettier@3.8.3)': + '@nestjs/cli@11.0.21(@swc/core@1.15.33(@swc/helpers@0.5.21))(@types/node@24.12.4)(esbuild@0.28.0)(lightningcss@1.32.0)(prettier@3.8.3)': dependencies: '@angular-devkit/core': 19.2.24(chokidar@4.0.3) '@angular-devkit/schematics': 19.2.24(chokidar@4.0.3) - '@angular-devkit/schematics-cli': 19.2.24(@types/node@24.12.2)(chokidar@4.0.3) - '@inquirer/prompts': 7.10.1(@types/node@24.12.2) + '@angular-devkit/schematics-cli': 19.2.24(@types/node@24.12.4)(chokidar@4.0.3) + '@inquirer/prompts': 7.10.1(@types/node@24.12.4) '@nestjs/schematics': 11.1.0(chokidar@4.0.3)(prettier@3.8.3)(typescript@5.9.3) ansis: 4.2.0 chokidar: 4.0.3 cli-table3: 0.6.5 commander: 4.1.1 - fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.9.3)(webpack@5.106.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0)) + fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.9.3)(webpack@5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.21))(esbuild@0.28.0)(lightningcss@1.32.0)) glob: 13.0.6 node-emoji: 1.11.0 ora: 5.4.1 tsconfig-paths: 4.2.0 tsconfig-paths-webpack-plugin: 4.2.0 typescript: 5.9.3 - webpack: 5.106.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0) + webpack: 5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.21))(esbuild@0.28.0)(lightningcss@1.32.0) webpack-node-externals: 3.0.0 optionalDependencies: - '@swc/core': 1.15.30(@swc/helpers@0.5.21) + '@swc/core': 1.15.33(@swc/helpers@0.5.21) transitivePeerDependencies: + - '@minify-html/node' + - '@swc/css' + - '@swc/html' - '@types/node' + - clean-css + - cssnano + - csso - esbuild + - html-minifier-terser + - lightningcss + - postcss - prettier - uglify-js - webpack-cli - '@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: file-type: 21.3.4 iterare: 1.2.1 @@ -16006,14 +16485,12 @@ snapshots: rxjs: 7.8.2 tslib: 2.8.1 uid: 2.0.2 - optionalDependencies: - class-transformer: 0.5.1 transitivePeerDependencies: - supports-color - '@nestjs/core@11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@nestjs/core@11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nuxt/opencollective': 0.4.1 fast-safe-stringify: 2.1.1 iterare: 1.2.1 @@ -16023,20 +16500,18 @@ snapshots: tslib: 2.8.1 uid: 2.0.2 optionalDependencies: - '@nestjs/platform-express': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) - '@nestjs/websockets': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/platform-express': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21) + '@nestjs/websockets': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(@nestjs/platform-socket.io@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/mapped-types@2.1.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(reflect-metadata@0.2.2)': + '@nestjs/mapped-types@2.1.1(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) reflect-metadata: 0.2.2 - optionalDependencies: - class-transformer: 0.5.1 - '@nestjs/platform-express@11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)': + '@nestjs/platform-express@11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) cors: 2.8.6 express: 5.2.1 multer: 2.1.1 @@ -16045,10 +16520,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@nestjs/platform-socket.io@11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.19)(rxjs@7.8.2)': + '@nestjs/platform-socket.io@11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.21)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/websockets': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/websockets': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(@nestjs/platform-socket.io@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) rxjs: 7.8.2 socket.io: 4.8.3 tslib: 2.8.1 @@ -16057,10 +16532,10 @@ snapshots: - supports-color - utf-8-validate - '@nestjs/schedule@6.1.3(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)': + '@nestjs/schedule@6.1.3(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) cron: 4.4.0 '@nestjs/schematics@11.1.0(chokidar@4.0.3)(prettier@3.8.3)(typescript@5.9.3)': @@ -16089,39 +16564,39 @@ snapshots: transitivePeerDependencies: - chokidar - '@nestjs/swagger@11.4.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(class-transformer@0.5.1)(reflect-metadata@0.2.2)': + '@nestjs/swagger@11.4.3(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(reflect-metadata@0.2.2)': dependencies: '@microsoft/tsdoc': 0.16.0 - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/mapped-types': 2.1.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(reflect-metadata@0.2.2) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/mapped-types': 2.1.1(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2) js-yaml: 4.1.1 lodash: 4.18.1 path-to-regexp: 8.4.2 reflect-metadata: 0.2.2 - swagger-ui-dist: 5.32.4 - optionalDependencies: - class-transformer: 0.5.1 + swagger-ui-dist: 5.32.6 - '@nestjs/testing@11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-express@11.1.19)': + '@nestjs/testing@11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(@nestjs/platform-express@11.1.21)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) tslib: 2.8.1 optionalDependencies: - '@nestjs/platform-express': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) + '@nestjs/platform-express': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21) - '@nestjs/websockets@11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@nestjs/websockets@11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(@nestjs/platform-socket.io@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) iterare: 1.2.1 object-hash: 3.0.0 reflect-metadata: 0.2.2 rxjs: 7.8.2 tslib: 2.8.1 optionalDependencies: - '@nestjs/platform-socket.io': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.19)(rxjs@7.8.2) + '@nestjs/platform-socket.io': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.21)(rxjs@7.8.2) + + '@noble/hashes@1.4.0': {} '@noble/hashes@1.8.0': {} @@ -16139,153 +16614,128 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 - '@npmcli/agent@4.0.0': - dependencies: - agent-base: 7.1.4 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 - lru-cache: 11.3.5 - socks-proxy-agent: 8.0.5 - transitivePeerDependencies: - - supports-color - - '@npmcli/fs@5.0.0': - dependencies: - semver: 7.7.4 - '@nuxt/opencollective@0.4.1': dependencies: consola: 3.4.2 '@oazapfts/runtime@1.2.0': {} - '@opentelemetry/api-logs@0.215.0': + '@opentelemetry/api-logs@0.218.0': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs@0.217.0': - dependencies: - '@opentelemetry/api': 1.9.1 - - '@opentelemetry/api@1.9.0': {} - '@opentelemetry/api@1.9.1': {} - '@opentelemetry/configuration@0.217.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/configuration@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - yaml: 2.8.3 + yaml: 2.9.0 '@opentelemetry/context-async-hooks@2.7.1(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core@2.7.0(@opentelemetry/api@1.9.1)': - dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/semantic-conventions': 1.40.0 - '@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/semantic-conventions': 1.41.1 - '@opentelemetry/exporter-logs-otlp-grpc@0.217.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-logs-otlp-grpc@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@grpc/grpc-js': 1.14.3 '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-grpc-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-grpc-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-logs-otlp-http@0.217.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-logs-otlp-http@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.217.0 + '@opentelemetry/api-logs': 0.218.0 '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-logs-otlp-proto@0.217.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-logs-otlp-proto@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.217.0 + '@opentelemetry/api-logs': 0.218.0 '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.218.0(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-grpc@0.217.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-metrics-otlp-grpc@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@grpc/grpc-js': 1.14.3 '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-http': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-grpc-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-http': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-grpc-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-http@0.217.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-metrics-otlp-http@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-proto@0.217.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-metrics-otlp-proto@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-http': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-http': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-prometheus@0.217.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-prometheus@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/semantic-conventions': 1.41.1 - '@opentelemetry/exporter-trace-otlp-grpc@0.217.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-trace-otlp-grpc@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@grpc/grpc-js': 1.14.3 '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-grpc-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-grpc-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-trace-otlp-http@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-trace-otlp-proto@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) @@ -16295,94 +16745,84 @@ snapshots: '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/semantic-conventions': 1.41.1 '@opentelemetry/host-metrics@0.38.3(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - systeminformation: 5.31.5 + systeminformation: 5.31.6 - '@opentelemetry/instrumentation-http@0.215.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-http@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 forwarded-parse: 2.1.2 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-ioredis@0.63.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-ioredis@0.66.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/instrumentation': 0.215.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.218.0(@opentelemetry/api@1.9.1) '@opentelemetry/redis-common': 0.38.3 - '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/semantic-conventions': 1.41.1 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-nestjs-core@0.61.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-nestjs-core@0.64.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/instrumentation': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/instrumentation': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-pg@0.67.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-pg@0.70.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/instrumentation': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.1) '@types/pg': 8.15.6 '@types/pg-pool': 2.0.7 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation@0.215.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.215.0 - import-in-the-middle: 3.0.0 + '@opentelemetry/api-logs': 0.218.0 + import-in-the-middle: 3.0.1 require-in-the-middle: 8.0.1 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation@0.217.0(@opentelemetry/api@1.9.1)': - dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.217.0 - import-in-the-middle: 3.0.0 - require-in-the-middle: 8.0.1 - transitivePeerDependencies: - - supports-color - - '@opentelemetry/otlp-exporter-base@0.217.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/otlp-exporter-base@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-grpc-exporter-base@0.217.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/otlp-grpc-exporter-base@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@grpc/grpc-js': 1.14.3 '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer@0.217.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/otlp-transformer@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.217.0 + '@opentelemetry/api-logs': 0.218.0 '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.218.0(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) - protobufjs: 8.0.1 '@opentelemetry/propagator-b3@2.7.1(@opentelemetry/api@1.9.1)': dependencies: @@ -16400,15 +16840,15 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/semantic-conventions': 1.41.1 - '@opentelemetry/sdk-logs@0.217.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/sdk-logs@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.217.0 + '@opentelemetry/api-logs': 0.218.0 '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/semantic-conventions': 1.41.1 '@opentelemetry/sdk-metrics@2.7.1(@opentelemetry/api@1.9.1)': dependencies: @@ -16416,34 +16856,34 @@ snapshots: '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-node@0.217.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/sdk-node@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.217.0 - '@opentelemetry/configuration': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/api-logs': 0.218.0 + '@opentelemetry/configuration': 0.218.0(@opentelemetry/api@1.9.1) '@opentelemetry/context-async-hooks': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-logs-otlp-grpc': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-logs-otlp-http': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-logs-otlp-proto': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-grpc': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-http': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-proto': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-prometheus': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-grpc': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-http': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-proto': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-logs-otlp-grpc': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-logs-otlp-http': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-logs-otlp-proto': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-grpc': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-http': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-proto': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-prometheus': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-grpc': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-http': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-proto': 0.218.0(@opentelemetry/api@1.9.1) '@opentelemetry/exporter-zipkin': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) '@opentelemetry/propagator-b3': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/propagator-jaeger': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.218.0(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-node': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/semantic-conventions': 1.41.1 transitivePeerDependencies: - supports-color @@ -16452,7 +16892,7 @@ snapshots: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/semantic-conventions': 1.41.1 '@opentelemetry/sdk-trace-node@2.7.1(@opentelemetry/api@1.9.1)': dependencies: @@ -16461,14 +16901,14 @@ snapshots: '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions@1.40.0': {} + '@opentelemetry/semantic-conventions@1.41.1': {} '@opentelemetry/sql-common@0.41.2(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@oxc-project/types@0.127.0': {} + '@oxc-project/types@0.130.0': {} '@paralleldrive/cuid2@2.3.1': dependencies: @@ -16535,6 +16975,100 @@ snapshots: '@parcel/watcher-win32-x64': 2.5.6 optional: true + '@peculiar/asn1-cms@2.7.0': + dependencies: + '@peculiar/asn1-schema': 2.7.0 + '@peculiar/asn1-x509': 2.7.0 + '@peculiar/asn1-x509-attr': 2.7.0 + asn1js: 3.0.10 + tslib: 2.8.1 + + '@peculiar/asn1-csr@2.7.0': + dependencies: + '@peculiar/asn1-schema': 2.7.0 + '@peculiar/asn1-x509': 2.7.0 + asn1js: 3.0.10 + tslib: 2.8.1 + + '@peculiar/asn1-ecc@2.7.0': + dependencies: + '@peculiar/asn1-schema': 2.7.0 + '@peculiar/asn1-x509': 2.7.0 + asn1js: 3.0.10 + tslib: 2.8.1 + + '@peculiar/asn1-pfx@2.7.0': + dependencies: + '@peculiar/asn1-cms': 2.7.0 + '@peculiar/asn1-pkcs8': 2.7.0 + '@peculiar/asn1-rsa': 2.7.0 + '@peculiar/asn1-schema': 2.7.0 + asn1js: 3.0.10 + tslib: 2.8.1 + + '@peculiar/asn1-pkcs8@2.7.0': + dependencies: + '@peculiar/asn1-schema': 2.7.0 + '@peculiar/asn1-x509': 2.7.0 + asn1js: 3.0.10 + tslib: 2.8.1 + + '@peculiar/asn1-pkcs9@2.7.0': + dependencies: + '@peculiar/asn1-cms': 2.7.0 + '@peculiar/asn1-pfx': 2.7.0 + '@peculiar/asn1-pkcs8': 2.7.0 + '@peculiar/asn1-schema': 2.7.0 + '@peculiar/asn1-x509': 2.7.0 + '@peculiar/asn1-x509-attr': 2.7.0 + asn1js: 3.0.10 + tslib: 2.8.1 + + '@peculiar/asn1-rsa@2.7.0': + dependencies: + '@peculiar/asn1-schema': 2.7.0 + '@peculiar/asn1-x509': 2.7.0 + asn1js: 3.0.10 + tslib: 2.8.1 + + '@peculiar/asn1-schema@2.7.0': + dependencies: + '@peculiar/utils': 2.0.3 + asn1js: 3.0.10 + tslib: 2.8.1 + + '@peculiar/asn1-x509-attr@2.7.0': + dependencies: + '@peculiar/asn1-schema': 2.7.0 + '@peculiar/asn1-x509': 2.7.0 + asn1js: 3.0.10 + tslib: 2.8.1 + + '@peculiar/asn1-x509@2.7.0': + dependencies: + '@peculiar/asn1-schema': 2.7.0 + '@peculiar/utils': 2.0.3 + asn1js: 3.0.10 + tslib: 2.8.1 + + '@peculiar/utils@2.0.3': + dependencies: + tslib: 2.8.1 + + '@peculiar/x509@1.14.3': + dependencies: + '@peculiar/asn1-cms': 2.7.0 + '@peculiar/asn1-csr': 2.7.0 + '@peculiar/asn1-ecc': 2.7.0 + '@peculiar/asn1-pkcs9': 2.7.0 + '@peculiar/asn1-rsa': 2.7.0 + '@peculiar/asn1-schema': 2.7.0 + '@peculiar/asn1-x509': 2.7.0 + pvtsutils: 1.3.6 + reflect-metadata: 0.2.2 + tslib: 2.8.1 + tsyringe: 4.10.0 + '@photo-sphere-viewer/core@5.14.1': dependencies: three: 0.179.1 @@ -16543,7 +17077,7 @@ snapshots: dependencies: '@photo-sphere-viewer/core': 5.14.1 '@photo-sphere-viewer/video-plugin': 5.14.1(@photo-sphere-viewer/core@5.14.1) - three: 0.182.0 + three: 0.184.0 '@photo-sphere-viewer/markers-plugin@5.14.1(@photo-sphere-viewer/core@5.14.1)': dependencies: @@ -16561,7 +17095,7 @@ snapshots: '@photo-sphere-viewer/video-plugin@5.14.1(@photo-sphere-viewer/core@5.14.1)': dependencies: '@photo-sphere-viewer/core': 5.14.1 - three: 0.182.0 + three: 0.184.0 '@photostructure/tz-lookup@11.5.0': {} @@ -16570,9 +17104,9 @@ snapshots: '@pkgr/core@0.2.9': {} - '@playwright/test@1.59.1': + '@playwright/test@1.60.0': dependencies: - playwright: 1.59.1 + playwright: 1.60.0 '@pnpm/config.env-replace@1.1.0': {} @@ -16580,7 +17114,7 @@ snapshots: dependencies: graceful-fs: 4.2.10 - '@pnpm/npm-conf@2.3.1': + '@pnpm/npm-conf@3.0.2': dependencies: '@pnpm/config.env-replace': 1.1.0 '@pnpm/network.ca-file': 1.0.2 @@ -16592,294 +17126,293 @@ snapshots: '@protobufjs/base64@1.1.2': {} - '@protobufjs/codegen@2.0.4': {} + '@protobufjs/codegen@2.0.5': {} '@protobufjs/eventemitter@1.1.0': {} - '@protobufjs/fetch@1.1.0': + '@protobufjs/fetch@1.1.1': dependencies: '@protobufjs/aspromise': 1.1.2 - '@protobufjs/inquire': 1.1.0 '@protobufjs/float@1.0.2': {} - '@protobufjs/inquire@1.1.0': {} + '@protobufjs/inquire@1.1.2': {} '@protobufjs/path@1.1.2': {} '@protobufjs/pool@1.1.0': {} - '@protobufjs/utf8@1.1.0': {} + '@protobufjs/utf8@1.1.1': {} - '@react-email/body@0.3.0(react@19.2.5)': + '@react-email/body@0.3.0(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/button@0.2.1(react@19.2.5)': + '@react-email/button@0.2.1(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/code-block@0.2.1(react@19.2.5)': + '@react-email/code-block@0.2.1(react@19.2.6)': dependencies: prismjs: 1.30.0 - react: 19.2.5 + react: 19.2.6 - '@react-email/code-inline@0.0.6(react@19.2.5)': + '@react-email/code-inline@0.0.6(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/column@0.0.14(react@19.2.5)': + '@react-email/column@0.0.14(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/components@1.0.12(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@react-email/components@1.0.12(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@react-email/body': 0.3.0(react@19.2.5) - '@react-email/button': 0.2.1(react@19.2.5) - '@react-email/code-block': 0.2.1(react@19.2.5) - '@react-email/code-inline': 0.0.6(react@19.2.5) - '@react-email/column': 0.0.14(react@19.2.5) - '@react-email/container': 0.0.16(react@19.2.5) - '@react-email/font': 0.0.10(react@19.2.5) - '@react-email/head': 0.0.13(react@19.2.5) - '@react-email/heading': 0.0.16(react@19.2.5) - '@react-email/hr': 0.0.12(react@19.2.5) - '@react-email/html': 0.0.12(react@19.2.5) - '@react-email/img': 0.0.12(react@19.2.5) - '@react-email/link': 0.0.13(react@19.2.5) - '@react-email/markdown': 0.0.18(react@19.2.5) - '@react-email/preview': 0.0.14(react@19.2.5) - '@react-email/render': 2.0.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-email/row': 0.0.13(react@19.2.5) - '@react-email/section': 0.0.17(react@19.2.5) - '@react-email/tailwind': 2.0.7(@react-email/body@0.3.0(react@19.2.5))(@react-email/button@0.2.1(react@19.2.5))(@react-email/code-block@0.2.1(react@19.2.5))(@react-email/code-inline@0.0.6(react@19.2.5))(@react-email/container@0.0.16(react@19.2.5))(@react-email/heading@0.0.16(react@19.2.5))(@react-email/hr@0.0.12(react@19.2.5))(@react-email/img@0.0.12(react@19.2.5))(@react-email/link@0.0.13(react@19.2.5))(@react-email/preview@0.0.14(react@19.2.5))(@react-email/text@0.1.6(react@19.2.5))(react@19.2.5) - '@react-email/text': 0.1.6(react@19.2.5) - react: 19.2.5 + '@react-email/body': 0.3.0(react@19.2.6) + '@react-email/button': 0.2.1(react@19.2.6) + '@react-email/code-block': 0.2.1(react@19.2.6) + '@react-email/code-inline': 0.0.6(react@19.2.6) + '@react-email/column': 0.0.14(react@19.2.6) + '@react-email/container': 0.0.16(react@19.2.6) + '@react-email/font': 0.0.10(react@19.2.6) + '@react-email/head': 0.0.13(react@19.2.6) + '@react-email/heading': 0.0.16(react@19.2.6) + '@react-email/hr': 0.0.12(react@19.2.6) + '@react-email/html': 0.0.12(react@19.2.6) + '@react-email/img': 0.0.12(react@19.2.6) + '@react-email/link': 0.0.13(react@19.2.6) + '@react-email/markdown': 0.0.18(react@19.2.6) + '@react-email/preview': 0.0.14(react@19.2.6) + '@react-email/render': 2.0.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@react-email/row': 0.0.13(react@19.2.6) + '@react-email/section': 0.0.17(react@19.2.6) + '@react-email/tailwind': 2.0.7(@react-email/body@0.3.0(react@19.2.6))(@react-email/button@0.2.1(react@19.2.6))(@react-email/code-block@0.2.1(react@19.2.6))(@react-email/code-inline@0.0.6(react@19.2.6))(@react-email/container@0.0.16(react@19.2.6))(@react-email/heading@0.0.16(react@19.2.6))(@react-email/hr@0.0.12(react@19.2.6))(@react-email/img@0.0.12(react@19.2.6))(@react-email/link@0.0.13(react@19.2.6))(@react-email/preview@0.0.14(react@19.2.6))(@react-email/text@0.1.6(react@19.2.6))(react@19.2.6) + '@react-email/text': 0.1.6(react@19.2.6) + react: 19.2.6 transitivePeerDependencies: - react-dom - '@react-email/container@0.0.16(react@19.2.5)': + '@react-email/container@0.0.16(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/font@0.0.10(react@19.2.5)': + '@react-email/font@0.0.10(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/head@0.0.13(react@19.2.5)': + '@react-email/head@0.0.13(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/heading@0.0.16(react@19.2.5)': + '@react-email/heading@0.0.16(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/hr@0.0.12(react@19.2.5)': + '@react-email/hr@0.0.12(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/html@0.0.12(react@19.2.5)': + '@react-email/html@0.0.12(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/img@0.0.12(react@19.2.5)': + '@react-email/img@0.0.12(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/link@0.0.13(react@19.2.5)': + '@react-email/link@0.0.13(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/markdown@0.0.18(react@19.2.5)': + '@react-email/markdown@0.0.18(react@19.2.6)': dependencies: marked: 15.0.12 - react: 19.2.5 + react: 19.2.6 - '@react-email/preview@0.0.14(react@19.2.5)': + '@react-email/preview@0.0.14(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/render@2.0.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@react-email/render@2.0.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: html-to-text: 9.0.5 prettier: 3.8.3 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) - '@react-email/render@2.0.7(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@react-email/render@2.0.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: html-to-text: 9.0.5 prettier: 3.8.3 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) - '@react-email/row@0.0.13(react@19.2.5)': + '@react-email/row@0.0.13(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/section@0.0.17(react@19.2.5)': + '@react-email/section@0.0.17(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/tailwind@2.0.7(@react-email/body@0.3.0(react@19.2.5))(@react-email/button@0.2.1(react@19.2.5))(@react-email/code-block@0.2.1(react@19.2.5))(@react-email/code-inline@0.0.6(react@19.2.5))(@react-email/container@0.0.16(react@19.2.5))(@react-email/heading@0.0.16(react@19.2.5))(@react-email/hr@0.0.12(react@19.2.5))(@react-email/img@0.0.12(react@19.2.5))(@react-email/link@0.0.13(react@19.2.5))(@react-email/preview@0.0.14(react@19.2.5))(@react-email/text@0.1.6(react@19.2.5))(react@19.2.5)': + '@react-email/tailwind@2.0.7(@react-email/body@0.3.0(react@19.2.6))(@react-email/button@0.2.1(react@19.2.6))(@react-email/code-block@0.2.1(react@19.2.6))(@react-email/code-inline@0.0.6(react@19.2.6))(@react-email/container@0.0.16(react@19.2.6))(@react-email/heading@0.0.16(react@19.2.6))(@react-email/hr@0.0.12(react@19.2.6))(@react-email/img@0.0.12(react@19.2.6))(@react-email/link@0.0.13(react@19.2.6))(@react-email/preview@0.0.14(react@19.2.6))(@react-email/text@0.1.6(react@19.2.6))(react@19.2.6)': dependencies: - '@react-email/text': 0.1.6(react@19.2.5) - react: 19.2.5 - tailwindcss: 4.2.4 + '@react-email/text': 0.1.6(react@19.2.6) + react: 19.2.6 + tailwindcss: 4.3.0 optionalDependencies: - '@react-email/body': 0.3.0(react@19.2.5) - '@react-email/button': 0.2.1(react@19.2.5) - '@react-email/code-block': 0.2.1(react@19.2.5) - '@react-email/code-inline': 0.0.6(react@19.2.5) - '@react-email/container': 0.0.16(react@19.2.5) - '@react-email/heading': 0.0.16(react@19.2.5) - '@react-email/hr': 0.0.12(react@19.2.5) - '@react-email/img': 0.0.12(react@19.2.5) - '@react-email/link': 0.0.13(react@19.2.5) - '@react-email/preview': 0.0.14(react@19.2.5) + '@react-email/body': 0.3.0(react@19.2.6) + '@react-email/button': 0.2.1(react@19.2.6) + '@react-email/code-block': 0.2.1(react@19.2.6) + '@react-email/code-inline': 0.0.6(react@19.2.6) + '@react-email/container': 0.0.16(react@19.2.6) + '@react-email/heading': 0.0.16(react@19.2.6) + '@react-email/hr': 0.0.12(react@19.2.6) + '@react-email/img': 0.0.12(react@19.2.6) + '@react-email/link': 0.0.13(react@19.2.6) + '@react-email/preview': 0.0.14(react@19.2.6) - '@react-email/text@0.1.6(react@19.2.5)': + '@react-email/text@0.1.6(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@replit/codemirror-indentation-markers@6.5.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.41.1)': + '@replit/codemirror-indentation-markers@6.5.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.43.0)': dependencies: '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 - '@codemirror/view': 6.41.1 + '@codemirror/view': 6.43.0 - '@rolldown/binding-android-arm64@1.0.0-rc.17': + '@rolldown/binding-android-arm64@1.0.1': optional: true - '@rolldown/binding-darwin-arm64@1.0.0-rc.17': + '@rolldown/binding-darwin-arm64@1.0.1': optional: true - '@rolldown/binding-darwin-x64@1.0.0-rc.17': + '@rolldown/binding-darwin-x64@1.0.1': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-rc.17': + '@rolldown/binding-freebsd-x64@1.0.1': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': + '@rolldown/binding-linux-arm-gnueabihf@1.0.1': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': + '@rolldown/binding-linux-arm64-gnu@1.0.1': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': + '@rolldown/binding-linux-arm64-musl@1.0.1': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': + '@rolldown/binding-linux-ppc64-gnu@1.0.1': optional: true - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': + '@rolldown/binding-linux-s390x-gnu@1.0.1': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': + '@rolldown/binding-linux-x64-gnu@1.0.1': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': + '@rolldown/binding-linux-x64-musl@1.0.1': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': + '@rolldown/binding-openharmony-arm64@1.0.1': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': + '@rolldown/binding-wasm32-wasi@1.0.1': dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': + '@rolldown/binding-win32-arm64-msvc@1.0.1': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': + '@rolldown/binding-win32-x64-msvc@1.0.1': optional: true - '@rolldown/pluginutils@1.0.0-rc.17': {} + '@rolldown/pluginutils@1.0.1': {} - '@rollup/pluginutils@5.3.0(rollup@4.55.1)': + '@rollup/pluginutils@5.3.0(rollup@4.60.4)': dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 estree-walker: 2.0.2 picomatch: 4.0.4 optionalDependencies: - rollup: 4.55.1 + rollup: 4.60.4 - '@rollup/rollup-android-arm-eabi@4.55.1': + '@rollup/rollup-android-arm-eabi@4.60.4': optional: true - '@rollup/rollup-android-arm64@4.55.1': + '@rollup/rollup-android-arm64@4.60.4': optional: true - '@rollup/rollup-darwin-arm64@4.55.1': + '@rollup/rollup-darwin-arm64@4.60.4': optional: true - '@rollup/rollup-darwin-x64@4.55.1': + '@rollup/rollup-darwin-x64@4.60.4': optional: true - '@rollup/rollup-freebsd-arm64@4.55.1': + '@rollup/rollup-freebsd-arm64@4.60.4': optional: true - '@rollup/rollup-freebsd-x64@4.55.1': + '@rollup/rollup-freebsd-x64@4.60.4': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.55.1': + '@rollup/rollup-linux-arm-gnueabihf@4.60.4': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.55.1': + '@rollup/rollup-linux-arm-musleabihf@4.60.4': optional: true - '@rollup/rollup-linux-arm64-gnu@4.55.1': + '@rollup/rollup-linux-arm64-gnu@4.60.4': optional: true - '@rollup/rollup-linux-arm64-musl@4.55.1': + '@rollup/rollup-linux-arm64-musl@4.60.4': optional: true - '@rollup/rollup-linux-loong64-gnu@4.55.1': + '@rollup/rollup-linux-loong64-gnu@4.60.4': optional: true - '@rollup/rollup-linux-loong64-musl@4.55.1': + '@rollup/rollup-linux-loong64-musl@4.60.4': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.55.1': + '@rollup/rollup-linux-ppc64-gnu@4.60.4': optional: true - '@rollup/rollup-linux-ppc64-musl@4.55.1': + '@rollup/rollup-linux-ppc64-musl@4.60.4': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.55.1': + '@rollup/rollup-linux-riscv64-gnu@4.60.4': optional: true - '@rollup/rollup-linux-riscv64-musl@4.55.1': + '@rollup/rollup-linux-riscv64-musl@4.60.4': optional: true - '@rollup/rollup-linux-s390x-gnu@4.55.1': + '@rollup/rollup-linux-s390x-gnu@4.60.4': optional: true - '@rollup/rollup-linux-x64-gnu@4.55.1': + '@rollup/rollup-linux-x64-gnu@4.60.4': optional: true - '@rollup/rollup-linux-x64-musl@4.55.1': + '@rollup/rollup-linux-x64-musl@4.60.4': optional: true - '@rollup/rollup-openbsd-x64@4.55.1': + '@rollup/rollup-openbsd-x64@4.60.4': optional: true - '@rollup/rollup-openharmony-arm64@4.55.1': + '@rollup/rollup-openharmony-arm64@4.60.4': optional: true - '@rollup/rollup-win32-arm64-msvc@4.55.1': + '@rollup/rollup-win32-arm64-msvc@4.60.4': optional: true - '@rollup/rollup-win32-ia32-msvc@4.55.1': + '@rollup/rollup-win32-ia32-msvc@4.60.4': optional: true - '@rollup/rollup-win32-x64-gnu@4.55.1': + '@rollup/rollup-win32-x64-gnu@4.60.4': optional: true - '@rollup/rollup-win32-x64-msvc@4.55.1': + '@rollup/rollup-win32-x64-msvc@4.60.4': optional: true '@scarf/scarf@1.4.0': {} @@ -16897,19 +17430,19 @@ snapshots: '@sideway/pinpoint@2.0.0': {} - '@sinclair/typebox@0.27.8': {} + '@sinclair/typebox@0.27.10': {} '@sindresorhus/is@4.6.0': {} '@sindresorhus/is@5.6.0': {} - '@slorber/react-helmet-async@1.3.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@slorber/react-helmet-async@1.3.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@babel/runtime': 7.29.2 invariant: 2.2.4 prop-types: 15.8.1 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) react-fast-compare: 3.2.2 shallowequal: 1.1.0 @@ -16921,11 +17454,11 @@ snapshots: '@socket.io/component-emitter@3.1.2': {} - '@socket.io/redis-adapter@8.3.0(socket.io-adapter@2.5.6)': + '@socket.io/redis-adapter@8.3.0(socket.io-adapter@2.5.7)': dependencies: debug: 4.3.7 notepack.io: 3.0.1 - socket.io-adapter: 2.5.6 + socket.io-adapter: 2.5.7 uid2: 1.0.0 transitivePeerDependencies: - supports-color @@ -16934,106 +17467,105 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@sveltejs/acorn-typescript@1.0.9(acorn@8.16.0)': + '@sveltejs/acorn-typescript@1.0.10(acorn@8.16.0)': dependencies: acorn: 8.16.0 - '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))': + '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))': dependencies: - '@sveltejs/kit': 2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@sveltejs/kit': 2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) - '@sveltejs/enhanced-img@0.10.4(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(rollup@4.55.1)(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + '@sveltejs/enhanced-img@0.10.4(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(rollup@4.60.4)(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0))': dependencies: - '@sveltejs/vite-plugin-svelte': 7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@sveltejs/vite-plugin-svelte': 7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) magic-string: 0.30.21 sharp: 0.34.5 - svelte: 5.55.2 - svelte-parse-markup: 0.1.5(svelte@5.55.2) - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - vite-imagetools: 9.0.3(rollup@4.55.1) + svelte: 5.55.8(@typescript-eslint/types@8.59.4) + svelte-parse-markup: 0.1.5(svelte@5.55.8(@typescript-eslint/types@8.59.4)) + vite: 8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) + vite-imagetools: 9.0.3(rollup@4.60.4) zimmerframe: 1.1.4 transitivePeerDependencies: - rollup - - supports-color - '@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + '@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0))': dependencies: '@standard-schema/spec': 1.1.0 - '@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0) - '@sveltejs/vite-plugin-svelte': 7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@sveltejs/acorn-typescript': 1.0.10(acorn@8.16.0) + '@sveltejs/vite-plugin-svelte': 7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) '@types/cookie': 0.6.0 acorn: 8.16.0 cookie: 0.6.0 - devalue: 5.7.1 + devalue: 5.8.1 esm-env: 1.2.2 kleur: 4.1.5 magic-string: 0.30.21 mrmime: 2.0.1 set-cookie-parser: 3.1.0 sirv: 3.0.2 - svelte: 5.55.2 - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + svelte: 5.55.8(@typescript-eslint/types@8.59.4) + vite: 8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) optionalDependencies: '@opentelemetry/api': 1.9.1 typescript: 6.0.3 - '@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + '@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0))': dependencies: deepmerge: 4.3.1 magic-string: 0.30.21 obug: 2.1.1 - svelte: 5.55.2 - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - vitefu: 1.1.2(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + svelte: 5.55.8(@typescript-eslint/types@8.59.4) + vite: 8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) + vitefu: 1.1.3(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) - '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.28.5)': + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 - '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.28.5)': + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 - '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.28.5)': + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 - '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.28.5)': + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 - '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.28.5)': + '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 - '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.28.5)': + '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 - '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.28.5)': + '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 - '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.28.5)': + '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 - '@svgr/babel-preset@8.1.0(@babel/core@7.28.5)': + '@svgr/babel-preset@8.1.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.28.5) - '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.28.5) - '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.28.5) - '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.28.5) - '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.28.5) - '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.28.5) - '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.28.5) - '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.29.0) + '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.29.0) '@svgr/core@8.1.0(typescript@6.0.3)': dependencies: - '@babel/core': 7.28.5 - '@svgr/babel-preset': 8.1.0(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@svgr/babel-preset': 8.1.0(@babel/core@7.29.0) camelcase: 6.3.0 cosmiconfig: 8.3.6(typescript@6.0.3) snake-case: 3.0.4 @@ -17048,8 +17580,8 @@ snapshots: '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@6.0.3))': dependencies: - '@babel/core': 7.28.5 - '@svgr/babel-preset': 8.1.0(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@svgr/babel-preset': 8.1.0(@babel/core@7.29.0) '@svgr/core': 8.1.0(typescript@6.0.3) '@svgr/hast-util-to-babel-ast': 8.0.0 svg-parser: 2.0.4 @@ -17061,17 +17593,17 @@ snapshots: '@svgr/core': 8.1.0(typescript@6.0.3) cosmiconfig: 8.3.6(typescript@6.0.3) deepmerge: 4.3.1 - svgo: 3.3.2 + svgo: 3.3.3 transitivePeerDependencies: - typescript '@svgr/webpack@8.1.0(typescript@6.0.3)': dependencies: - '@babel/core': 7.28.5 - '@babel/plugin-transform-react-constant-elements': 7.27.1(@babel/core@7.28.5) - '@babel/preset-env': 7.28.5(@babel/core@7.28.5) - '@babel/preset-react': 7.28.5(@babel/core@7.28.5) - '@babel/preset-typescript': 7.28.5(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-constant-elements': 7.27.1(@babel/core@7.29.0) + '@babel/preset-env': 7.29.5(@babel/core@7.29.0) + '@babel/preset-react': 7.28.5(@babel/core@7.29.0) + '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) '@svgr/core': 8.1.0(typescript@6.0.3) '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@6.0.3)) '@svgr/plugin-svgo': 8.1.0(@svgr/core@8.1.0(typescript@6.0.3))(typescript@6.0.3) @@ -17079,59 +17611,59 @@ snapshots: - supports-color - typescript - '@swc/core-darwin-arm64@1.15.30': + '@swc/core-darwin-arm64@1.15.33': optional: true - '@swc/core-darwin-x64@1.15.30': + '@swc/core-darwin-x64@1.15.33': optional: true - '@swc/core-linux-arm-gnueabihf@1.15.30': + '@swc/core-linux-arm-gnueabihf@1.15.33': optional: true - '@swc/core-linux-arm64-gnu@1.15.30': + '@swc/core-linux-arm64-gnu@1.15.33': optional: true - '@swc/core-linux-arm64-musl@1.15.30': + '@swc/core-linux-arm64-musl@1.15.33': optional: true - '@swc/core-linux-ppc64-gnu@1.15.30': + '@swc/core-linux-ppc64-gnu@1.15.33': optional: true - '@swc/core-linux-s390x-gnu@1.15.30': + '@swc/core-linux-s390x-gnu@1.15.33': optional: true - '@swc/core-linux-x64-gnu@1.15.30': + '@swc/core-linux-x64-gnu@1.15.33': optional: true - '@swc/core-linux-x64-musl@1.15.30': + '@swc/core-linux-x64-musl@1.15.33': optional: true - '@swc/core-win32-arm64-msvc@1.15.30': + '@swc/core-win32-arm64-msvc@1.15.33': optional: true - '@swc/core-win32-ia32-msvc@1.15.30': + '@swc/core-win32-ia32-msvc@1.15.33': optional: true - '@swc/core-win32-x64-msvc@1.15.30': + '@swc/core-win32-x64-msvc@1.15.33': optional: true - '@swc/core@1.15.30(@swc/helpers@0.5.21)': + '@swc/core@1.15.33(@swc/helpers@0.5.21)': dependencies: '@swc/counter': 0.1.3 '@swc/types': 0.1.26 optionalDependencies: - '@swc/core-darwin-arm64': 1.15.30 - '@swc/core-darwin-x64': 1.15.30 - '@swc/core-linux-arm-gnueabihf': 1.15.30 - '@swc/core-linux-arm64-gnu': 1.15.30 - '@swc/core-linux-arm64-musl': 1.15.30 - '@swc/core-linux-ppc64-gnu': 1.15.30 - '@swc/core-linux-s390x-gnu': 1.15.30 - '@swc/core-linux-x64-gnu': 1.15.30 - '@swc/core-linux-x64-musl': 1.15.30 - '@swc/core-win32-arm64-msvc': 1.15.30 - '@swc/core-win32-ia32-msvc': 1.15.30 - '@swc/core-win32-x64-msvc': 1.15.30 + '@swc/core-darwin-arm64': 1.15.33 + '@swc/core-darwin-x64': 1.15.33 + '@swc/core-linux-arm-gnueabihf': 1.15.33 + '@swc/core-linux-arm64-gnu': 1.15.33 + '@swc/core-linux-arm64-musl': 1.15.33 + '@swc/core-linux-ppc64-gnu': 1.15.33 + '@swc/core-linux-s390x-gnu': 1.15.33 + '@swc/core-linux-x64-gnu': 1.15.33 + '@swc/core-linux-x64-musl': 1.15.33 + '@swc/core-win32-arm64-msvc': 1.15.33 + '@swc/core-win32-ia32-msvc': 1.15.33 + '@swc/core-win32-x64-msvc': 1.15.33 '@swc/helpers': 0.5.21 '@swc/counter@0.1.3': {} @@ -17148,73 +17680,73 @@ snapshots: dependencies: defer-to-connect: 2.0.1 - '@tailwindcss/node@4.2.4': + '@tailwindcss/node@4.3.0': dependencies: '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.21.0 - jiti: 2.6.1 + enhanced-resolve: 5.21.5 + jiti: 2.7.0 lightningcss: 1.32.0 magic-string: 0.30.21 source-map-js: 1.2.1 - tailwindcss: 4.2.4 + tailwindcss: 4.3.0 - '@tailwindcss/oxide-android-arm64@4.2.4': + '@tailwindcss/oxide-android-arm64@4.3.0': optional: true - '@tailwindcss/oxide-darwin-arm64@4.2.4': + '@tailwindcss/oxide-darwin-arm64@4.3.0': optional: true - '@tailwindcss/oxide-darwin-x64@4.2.4': + '@tailwindcss/oxide-darwin-x64@4.3.0': optional: true - '@tailwindcss/oxide-freebsd-x64@4.2.4': + '@tailwindcss/oxide-freebsd-x64@4.3.0': optional: true - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0': optional: true - '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': + '@tailwindcss/oxide-linux-arm64-gnu@4.3.0': optional: true - '@tailwindcss/oxide-linux-arm64-musl@4.2.4': + '@tailwindcss/oxide-linux-arm64-musl@4.3.0': optional: true - '@tailwindcss/oxide-linux-x64-gnu@4.2.4': + '@tailwindcss/oxide-linux-x64-gnu@4.3.0': optional: true - '@tailwindcss/oxide-linux-x64-musl@4.2.4': + '@tailwindcss/oxide-linux-x64-musl@4.3.0': optional: true - '@tailwindcss/oxide-wasm32-wasi@4.2.4': + '@tailwindcss/oxide-wasm32-wasi@4.3.0': optional: true - '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': + '@tailwindcss/oxide-win32-arm64-msvc@4.3.0': optional: true - '@tailwindcss/oxide-win32-x64-msvc@4.2.4': + '@tailwindcss/oxide-win32-x64-msvc@4.3.0': optional: true - '@tailwindcss/oxide@4.2.4': + '@tailwindcss/oxide@4.3.0': optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.2.4 - '@tailwindcss/oxide-darwin-arm64': 4.2.4 - '@tailwindcss/oxide-darwin-x64': 4.2.4 - '@tailwindcss/oxide-freebsd-x64': 4.2.4 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.4 - '@tailwindcss/oxide-linux-arm64-gnu': 4.2.4 - '@tailwindcss/oxide-linux-arm64-musl': 4.2.4 - '@tailwindcss/oxide-linux-x64-gnu': 4.2.4 - '@tailwindcss/oxide-linux-x64-musl': 4.2.4 - '@tailwindcss/oxide-wasm32-wasi': 4.2.4 - '@tailwindcss/oxide-win32-arm64-msvc': 4.2.4 - '@tailwindcss/oxide-win32-x64-msvc': 4.2.4 + '@tailwindcss/oxide-android-arm64': 4.3.0 + '@tailwindcss/oxide-darwin-arm64': 4.3.0 + '@tailwindcss/oxide-darwin-x64': 4.3.0 + '@tailwindcss/oxide-freebsd-x64': 4.3.0 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.0 + '@tailwindcss/oxide-linux-arm64-gnu': 4.3.0 + '@tailwindcss/oxide-linux-arm64-musl': 4.3.0 + '@tailwindcss/oxide-linux-x64-gnu': 4.3.0 + '@tailwindcss/oxide-linux-x64-musl': 4.3.0 + '@tailwindcss/oxide-wasm32-wasi': 4.3.0 + '@tailwindcss/oxide-win32-arm64-msvc': 4.3.0 + '@tailwindcss/oxide-win32-x64-msvc': 4.3.0 - '@tailwindcss/vite@4.2.4(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + '@tailwindcss/vite@4.3.0(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0))': dependencies: - '@tailwindcss/node': 4.2.4 - '@tailwindcss/oxide': 4.2.4 - tailwindcss: 4.2.4 - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + '@tailwindcss/node': 4.3.0 + '@tailwindcss/oxide': 4.3.0 + tailwindcss: 4.3.0 + vite: 8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) '@testing-library/dom@10.4.1': dependencies: @@ -17236,18 +17768,18 @@ snapshots: picocolors: 1.1.1 redent: 3.0.0 - '@testing-library/svelte-core@1.0.0(svelte@5.55.2)': + '@testing-library/svelte-core@1.0.0(svelte@5.55.8(@typescript-eslint/types@8.59.4))': dependencies: - svelte: 5.55.2 + svelte: 5.55.8(@typescript-eslint/types@8.59.4) - '@testing-library/svelte@5.3.1(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5)': + '@testing-library/svelte@5.3.1(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0))(vitest@4.1.7)': dependencies: '@testing-library/dom': 10.4.1 - '@testing-library/svelte-core': 1.0.0(svelte@5.55.2) - svelte: 5.55.2 + '@testing-library/svelte-core': 1.0.0(svelte@5.55.8(@typescript-eslint/types@8.59.4)) + svelte: 5.55.8(@typescript-eslint/types@8.59.4) optionalDependencies: - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@3.2.3))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vite: 8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) + vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@24.12.4)(@vitest/coverage-v8@4.1.7)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@3.2.3))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': dependencies: @@ -17262,10 +17794,10 @@ snapshots: '@tokenizer/token@0.3.0': {} - '@trivago/prettier-plugin-sort-imports@6.0.2(prettier-plugin-svelte@3.5.1(prettier@3.8.3)(svelte@5.55.2))(prettier@3.8.3)(svelte@5.55.2)': + '@trivago/prettier-plugin-sort-imports@6.0.2(prettier-plugin-svelte@3.5.2(prettier@3.8.3)(svelte@5.55.8(@typescript-eslint/types@8.59.4)))(prettier@3.8.3)(svelte@5.55.8(@typescript-eslint/types@8.59.4))': dependencies: '@babel/generator': 7.29.1 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 javascript-natural-sort: 0.7.1 @@ -17274,40 +17806,38 @@ snapshots: parse-imports-exports: 0.2.4 prettier: 3.8.3 optionalDependencies: - prettier-plugin-svelte: 3.5.1(prettier@3.8.3)(svelte@5.55.2) - svelte: 5.55.2 + prettier-plugin-svelte: 3.5.2(prettier@3.8.3)(svelte@5.55.8(@typescript-eslint/types@8.59.4)) + svelte: 5.55.8(@typescript-eslint/types@8.59.4) transitivePeerDependencies: - supports-color - '@trysound/sax@0.2.0': {} - - '@turf/boolean-point-in-polygon@7.3.4': + '@turf/boolean-point-in-polygon@7.3.5': dependencies: - '@turf/helpers': 7.3.4 - '@turf/invariant': 7.3.4 + '@turf/helpers': 7.3.5 + '@turf/invariant': 7.3.5 '@types/geojson': 7946.0.16 point-in-polygon-hao: 1.2.4 tslib: 2.8.1 - '@turf/helpers@7.3.4': + '@turf/helpers@7.3.5': dependencies: '@types/geojson': 7946.0.16 tslib: 2.8.1 - '@turf/invariant@7.3.4': + '@turf/invariant@7.3.5': dependencies: - '@turf/helpers': 7.3.4 + '@turf/helpers': 7.3.5 '@types/geojson': 7946.0.16 tslib: 2.8.1 - '@tybys/wasm-util@0.10.1': + '@tybys/wasm-util@0.10.2': dependencies: tslib: 2.8.1 optional: true '@types/accepts@1.3.7': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/archiver@7.0.0': dependencies: @@ -17319,16 +17849,16 @@ snapshots: '@types/bcrypt@6.0.0': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/body-parser@1.19.6': dependencies: '@types/connect': 3.4.38 - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/bonjour@3.5.13': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/braces@3.0.5': {} @@ -17339,32 +17869,32 @@ snapshots: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 - '@types/chrome@0.1.32': + '@types/chrome@0.1.42': dependencies: '@types/filesystem': 0.0.36 '@types/har-format': 1.2.16 '@types/chromecast-caf-sender@1.0.11': dependencies: - '@types/chrome': 0.1.32 + '@types/chrome': 0.1.42 '@types/cli-progress@3.11.6': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/compression@1.8.1': dependencies: '@types/express': 5.0.6 - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/connect-history-api-fallback@1.5.4': dependencies: - '@types/express-serve-static-core': 5.1.0 - '@types/node': 24.12.2 + '@types/express-serve-static-core': 5.1.1 + '@types/node': 24.12.4 '@types/connect@3.4.38': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/content-disposition@0.5.9': {} @@ -17381,11 +17911,11 @@ snapshots: '@types/connect': 3.4.38 '@types/express': 5.0.6 '@types/keygrip': 1.0.6 - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/cors@2.8.19': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/d3-array@3.2.2': {} @@ -17504,7 +18034,7 @@ snapshots: '@types/d3-transition': 3.0.9 '@types/d3-zoom': 3.0.8 - '@types/debug@4.1.12': + '@types/debug@4.1.13': dependencies: '@types/ms': 2.1.0 @@ -17512,13 +18042,13 @@ snapshots: '@types/docker-modem@3.0.6': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/ssh2': 1.15.5 '@types/dockerode@4.0.1': dependencies: '@types/docker-modem': 3.0.6 - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/ssh2': 1.15.5 '@types/dom-to-image@2.6.7': {} @@ -17526,46 +18056,48 @@ snapshots: '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 9.6.1 - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/eslint@9.6.1': dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/json-schema': 7.0.15 '@types/esrecurse@4.3.1': {} '@types/estree-jsx@1.0.5': dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/estree@1.0.8': {} - '@types/express-serve-static-core@4.19.7': + '@types/estree@1.0.9': {} + + '@types/express-serve-static-core@4.19.8': dependencies: - '@types/node': 24.12.2 - '@types/qs': 6.14.0 + '@types/node': 24.12.4 + '@types/qs': 6.15.1 '@types/range-parser': 1.2.7 '@types/send': 1.2.1 - '@types/express-serve-static-core@5.1.0': + '@types/express-serve-static-core@5.1.1': dependencies: - '@types/node': 24.12.2 - '@types/qs': 6.14.0 + '@types/node': 24.12.4 + '@types/qs': 6.15.1 '@types/range-parser': 1.2.7 '@types/send': 1.2.1 '@types/express@4.17.25': dependencies: '@types/body-parser': 1.19.6 - '@types/express-serve-static-core': 4.19.7 - '@types/qs': 6.14.0 + '@types/express-serve-static-core': 4.19.8 + '@types/qs': 6.15.1 '@types/serve-static': 1.15.10 '@types/express@5.0.6': dependencies: '@types/body-parser': 1.19.6 - '@types/express-serve-static-core': 5.1.0 + '@types/express-serve-static-core': 5.1.1 '@types/serve-static': 2.2.0 '@types/filesystem@0.0.36': @@ -17576,7 +18108,7 @@ snapshots: '@types/fluent-ffmpeg@2.1.28': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/geojson@7946.0.16': {} @@ -17598,13 +18130,13 @@ snapshots: '@types/http-assert@1.5.6': {} - '@types/http-cache-semantics@4.0.4': {} + '@types/http-cache-semantics@4.2.0': {} '@types/http-errors@2.0.5': {} '@types/http-proxy@1.17.17': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/inquirer@8.2.12': dependencies: @@ -17628,7 +18160,7 @@ snapshots: '@types/jsonwebtoken@9.0.10': dependencies: '@types/ms': 2.1.0 - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/justified-layout@4.1.4': {} @@ -17636,9 +18168,9 @@ snapshots: '@types/koa-compose@3.2.9': dependencies: - '@types/koa': 3.0.1 + '@types/koa': 3.0.3 - '@types/koa@3.0.1': + '@types/koa@3.0.3': dependencies: '@types/accepts': 1.3.7 '@types/content-disposition': 0.5.9 @@ -17647,7 +18179,7 @@ snapshots: '@types/http-errors': 2.0.5 '@types/keygrip': 1.0.6 '@types/koa-compose': 3.2.9 - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/leaflet@1.9.21': dependencies: @@ -17677,7 +18209,7 @@ snapshots: '@types/mock-fs@4.13.4': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/ms@2.1.0': {} @@ -17685,34 +18217,25 @@ snapshots: dependencies: '@types/express': 5.0.6 - '@types/node-forge@1.3.14': - dependencies: - '@types/node': 24.12.2 - '@types/node@17.0.45': {} '@types/node@18.19.130': dependencies: undici-types: 5.26.5 - '@types/node@24.12.2': + '@types/node@24.12.4': dependencies: undici-types: 7.16.0 - '@types/node@25.6.0': - dependencies: - undici-types: 7.19.2 - optional: true - '@types/nodemailer@8.0.0': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/oidc-provider@9.5.0': dependencies: '@types/keygrip': 1.0.6 - '@types/koa': 3.0.1 - '@types/node': 24.12.2 + '@types/koa': 3.0.3 + '@types/node': 24.12.4 '@types/parse5@5.0.3': {} @@ -17722,73 +18245,73 @@ snapshots: '@types/pg@8.15.6': dependencies: - '@types/node': 24.12.2 - pg-protocol: 1.13.0 + '@types/node': 24.12.4 + pg-protocol: 1.14.0 pg-types: 2.2.0 '@types/pg@8.20.0': dependencies: - '@types/node': 24.12.2 - pg-protocol: 1.13.0 + '@types/node': 24.12.4 + pg-protocol: 1.14.0 pg-types: 2.2.0 '@types/picomatch@4.0.3': {} '@types/pngjs@6.0.5': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@types/prismjs@1.26.5': {} + '@types/prismjs@1.26.6': {} '@types/qrcode@1.5.6': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@types/qs@6.14.0': {} + '@types/qs@6.15.1': {} '@types/range-parser@1.2.7': {} '@types/react-router-config@5.0.11': dependencies: '@types/history': 4.7.11 - '@types/react': 19.2.14 + '@types/react': 19.2.15 '@types/react-router': 5.1.20 '@types/react-router-dom@5.3.3': dependencies: '@types/history': 4.7.11 - '@types/react': 19.2.14 + '@types/react': 19.2.15 '@types/react-router': 5.1.20 '@types/react-router@5.1.20': dependencies: '@types/history': 4.7.11 - '@types/react': 19.2.14 + '@types/react': 19.2.15 - '@types/react@19.2.14': + '@types/react@19.2.15': dependencies: csstype: 3.2.3 '@types/readdir-glob@1.1.5': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/retry@0.12.2': {} '@types/sax@1.2.7': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/semver@7.7.1': {} '@types/send@0.17.6': dependencies: '@types/mime': 1.3.5 - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/send@1.2.1': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/serve-index@1.9.4': dependencies: @@ -17797,36 +18320,36 @@ snapshots: '@types/serve-static@1.15.10': dependencies: '@types/http-errors': 2.0.5 - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/send': 0.17.6 '@types/serve-static@2.2.0': dependencies: '@types/http-errors': 2.0.5 - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/sockjs@0.3.36': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/ssh2-streams@0.1.13': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/ssh2@0.5.52': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/ssh2-streams': 0.1.13 '@types/ssh2@1.15.5': dependencies: '@types/node': 18.19.130 - '@types/superagent@8.1.9': + '@types/superagent@8.1.10': dependencies: '@types/cookiejar': 2.1.5 '@types/methods': 1.1.4 - '@types/node': 24.12.2 + '@types/node': 24.12.4 form-data: 4.0.5 '@types/supercluster@7.1.3': @@ -17836,11 +18359,11 @@ snapshots: '@types/supertest@7.2.0': dependencies: '@types/methods': 1.1.4 - '@types/superagent': 8.1.9 + '@types/superagent': 8.1.10 '@types/through@0.0.33': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/trusted-types@2.0.7': {} @@ -17856,7 +18379,7 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/yargs-parser@21.0.3': {} @@ -17864,15 +18387,15 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.59.0(@typescript-eslint/parser@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.59.4(@typescript-eslint/parser@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.59.0 - '@typescript-eslint/type-utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.59.0 - eslint: 10.2.1(jiti@2.6.1) + '@typescript-eslint/parser': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.59.4 + '@typescript-eslint/type-utils': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.59.4 + eslint: 10.4.0(jiti@2.7.0) ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -17880,90 +18403,93 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': + '@typescript-eslint/parser@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.59.0 - '@typescript-eslint/types': 8.59.0 - '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.59.0 + '@typescript-eslint/scope-manager': 8.59.4 + '@typescript-eslint/types': 8.59.4 + '@typescript-eslint/typescript-estree': 8.59.4(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.59.4 debug: 4.4.3 - eslint: 10.2.1(jiti@2.6.1) + eslint: 10.4.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.59.0(typescript@6.0.3)': + '@typescript-eslint/project-service@8.59.4(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.59.0(typescript@6.0.3) - '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/tsconfig-utils': 8.59.4(typescript@6.0.3) + '@typescript-eslint/types': 8.59.4 debug: 4.4.3 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.59.0': + '@typescript-eslint/scope-manager@8.59.4': dependencies: - '@typescript-eslint/types': 8.59.0 - '@typescript-eslint/visitor-keys': 8.59.0 + '@typescript-eslint/types': 8.59.4 + '@typescript-eslint/visitor-keys': 8.59.4 - '@typescript-eslint/tsconfig-utils@8.59.0(typescript@6.0.3)': + '@typescript-eslint/tsconfig-utils@8.59.4(typescript@6.0.3)': dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.59.0 - '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/types': 8.59.4 + '@typescript-eslint/typescript-estree': 8.59.4(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) debug: 4.4.3 - eslint: 10.2.1(jiti@2.6.1) + eslint: 10.4.0(jiti@2.7.0) ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.59.0': {} + '@typescript-eslint/types@8.59.4': {} - '@typescript-eslint/typescript-estree@8.59.0(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.59.4(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.59.0(typescript@6.0.3) - '@typescript-eslint/tsconfig-utils': 8.59.0(typescript@6.0.3) - '@typescript-eslint/types': 8.59.0 - '@typescript-eslint/visitor-keys': 8.59.0 + '@typescript-eslint/project-service': 8.59.4(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.59.4(typescript@6.0.3) + '@typescript-eslint/types': 8.59.4 + '@typescript-eslint/visitor-keys': 8.59.4 debug: 4.4.3 minimatch: 10.2.5 - semver: 7.7.4 + semver: 7.8.0 tinyglobby: 0.2.16 ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': + '@typescript-eslint/utils@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.59.0 - '@typescript-eslint/types': 8.59.0 - '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) - eslint: 10.2.1(jiti@2.6.1) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) + '@typescript-eslint/scope-manager': 8.59.4 + '@typescript-eslint/types': 8.59.4 + '@typescript-eslint/typescript-estree': 8.59.4(typescript@6.0.3) + eslint: 10.4.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.59.0': + '@typescript-eslint/visitor-keys@8.59.4': dependencies: - '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/types': 8.59.4 eslint-visitor-keys: 5.0.1 - '@ungap/structured-clone@1.3.0': {} + '@ungap/structured-clone@1.3.1': {} - '@valibot/to-json-schema@1.6.0(valibot@1.3.1(typescript@6.0.3))': + '@upsetjs/venn.js@2.0.0': + optionalDependencies: + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + '@valibot/to-json-schema@1.7.0(valibot@1.4.0(typescript@6.0.3))': dependencies: - valibot: 1.3.1(typescript@6.0.3) + valibot: 1.4.0(typescript@6.0.3) - '@vercel/oidc@3.0.5': {} - - '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.12.2)(happy-dom@20.9.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@3.2.3))(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.13)(@types/node@24.12.4)(happy-dom@20.9.0)(jiti@2.7.0)(jsdom@26.1.0(canvas@3.2.3))(lightningcss@1.32.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 @@ -17978,23 +18504,23 @@ snapshots: std-env: 3.10.0 test-exclude: 7.0.2 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.12.2)(happy-dom@20.9.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@3.2.3))(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vitest: 3.2.4(@types/debug@4.1.13)(@types/node@24.12.4)(happy-dom@20.9.0)(jiti@2.7.0)(jsdom@26.1.0(canvas@3.2.3))(lightningcss@1.32.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@4.1.5(vitest@4.1.5)': + '@vitest/coverage-v8@4.1.7(vitest@4.1.7)': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.1.5 + '@vitest/utils': 4.1.7 ast-v8-to-istanbul: 1.0.0 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-reports: 3.2.0 - magicast: 0.5.2 + magicast: 0.5.3 obug: 2.1.1 std-env: 4.1.0 tinyrainbow: 3.1.0 - vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@3.2.3))(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@24.12.4)(@vitest/coverage-v8@4.1.7)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@3.2.3))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) '@vitest/expect@3.2.4': dependencies: @@ -18004,44 +18530,36 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/expect@4.1.5': + '@vitest/expect@4.1.7': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.1.5 - '@vitest/utils': 4.1.5 + '@vitest/spy': 4.1.7 + '@vitest/utils': 4.1.7 chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@3.2.4(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + '@vitest/mocker@3.2.4(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 7.3.3(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) - '@vitest/mocker@4.1.5(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + '@vitest/mocker@4.1.7(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0))': dependencies: - '@vitest/spy': 4.1.5 + '@vitest/spy': 4.1.7 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - - '@vitest/mocker@4.1.5(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': - dependencies: - '@vitest/spy': 4.1.5 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 - '@vitest/pretty-format@4.1.5': + '@vitest/pretty-format@4.1.7': dependencies: tinyrainbow: 3.1.0 @@ -18051,9 +18569,9 @@ snapshots: pathe: 2.0.3 strip-literal: 3.1.0 - '@vitest/runner@4.1.5': + '@vitest/runner@4.1.7': dependencies: - '@vitest/utils': 4.1.5 + '@vitest/utils': 4.1.7 pathe: 2.0.3 '@vitest/snapshot@3.2.4': @@ -18062,10 +18580,10 @@ snapshots: magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/snapshot@4.1.5': + '@vitest/snapshot@4.1.7': dependencies: - '@vitest/pretty-format': 4.1.5 - '@vitest/utils': 4.1.5 + '@vitest/pretty-format': 4.1.7 + '@vitest/utils': 4.1.7 magic-string: 0.30.21 pathe: 2.0.3 @@ -18073,7 +18591,7 @@ snapshots: dependencies: tinyspy: 4.0.4 - '@vitest/spy@4.1.5': {} + '@vitest/spy@4.1.7': {} '@vitest/utils@3.2.4': dependencies: @@ -18081,9 +18599,9 @@ snapshots: loupe: 3.2.1 tinyrainbow: 2.0.0 - '@vitest/utils@4.1.5': + '@vitest/utils@4.1.7': dependencies: - '@vitest/pretty-format': 4.1.5 + '@vitest/pretty-format': 4.1.7 convert-source-map: 2.0.0 tinyrainbow: 3.1.0 @@ -18171,10 +18689,10 @@ snapshots: dependencies: '@namnode/store': 0.1.0 - '@zoom-image/svelte@0.3.9(svelte@5.55.2)': + '@zoom-image/svelte@0.3.9(svelte@5.55.8(@typescript-eslint/types@8.59.4))': dependencies: '@zoom-image/core': 0.42.0 - svelte: 5.55.2 + svelte: 5.55.8(@typescript-eslint/types@8.59.4) abbrev@1.1.1: {} @@ -18206,7 +18724,7 @@ snapshots: dependencies: acorn: 8.16.0 - acorn-walk@8.3.4: + acorn-walk@8.3.5: dependencies: acorn: 8.16.0 @@ -18220,39 +18738,36 @@ snapshots: transitivePeerDependencies: - supports-color - agent-base@7.1.4: {} + agent-base@7.1.4: + optional: true aggregate-error@3.1.0: dependencies: clean-stack: 2.2.0 indent-string: 4.0.0 - ai@5.0.113(zod@4.3.6): - dependencies: - '@ai-sdk/gateway': 2.0.21(zod@4.3.6) - '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.19(zod@4.3.6) - '@opentelemetry/api': 1.9.0 - zod: 4.3.6 - - ajv-formats@2.1.1(ajv@8.18.0): + ajv-formats@2.1.1(ajv@8.20.0): optionalDependencies: - ajv: 8.18.0 + ajv: 8.20.0 ajv-formats@3.0.1(ajv@8.18.0): optionalDependencies: ajv: 8.18.0 - ajv-keywords@3.5.2(ajv@6.14.0): - dependencies: - ajv: 6.14.0 + ajv-formats@3.0.1(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 - ajv-keywords@5.1.0(ajv@8.18.0): + ajv-keywords@3.5.2(ajv@6.15.0): dependencies: - ajv: 8.18.0 + ajv: 6.15.0 + + ajv-keywords@5.1.0(ajv@8.20.0): + dependencies: + ajv: 8.20.0 fast-deep-equal: 3.1.3 - ajv@6.14.0: + ajv@6.15.0: dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 @@ -18262,31 +18777,38 @@ snapshots: ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.0 + fast-uri: 3.1.2 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - algoliasearch-helper@3.26.1(algoliasearch@5.46.0): + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.2 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + algoliasearch-helper@3.29.1(algoliasearch@5.52.1): dependencies: '@algolia/events': 4.0.1 - algoliasearch: 5.46.0 + algoliasearch: 5.52.1 - algoliasearch@5.46.0: + algoliasearch@5.52.1: dependencies: - '@algolia/abtesting': 1.12.0 - '@algolia/client-abtesting': 5.46.0 - '@algolia/client-analytics': 5.46.0 - '@algolia/client-common': 5.46.0 - '@algolia/client-insights': 5.46.0 - '@algolia/client-personalization': 5.46.0 - '@algolia/client-query-suggestions': 5.46.0 - '@algolia/client-search': 5.46.0 - '@algolia/ingestion': 1.46.0 - '@algolia/monitoring': 1.46.0 - '@algolia/recommend': 5.46.0 - '@algolia/requester-browser-xhr': 5.46.0 - '@algolia/requester-fetch': 5.46.0 - '@algolia/requester-node-http': 5.46.0 + '@algolia/abtesting': 1.18.1 + '@algolia/client-abtesting': 5.52.1 + '@algolia/client-analytics': 5.52.1 + '@algolia/client-common': 5.52.1 + '@algolia/client-insights': 5.52.1 + '@algolia/client-personalization': 5.52.1 + '@algolia/client-query-suggestions': 5.52.1 + '@algolia/client-search': 5.52.1 + '@algolia/ingestion': 1.52.1 + '@algolia/monitoring': 1.52.1 + '@algolia/recommend': 5.52.1 + '@algolia/requester-browser-xhr': 5.52.1 + '@algolia/requester-fetch': 5.52.1 + '@algolia/requester-node-http': 5.52.1 ansi-align@3.0.1: dependencies: @@ -18344,7 +18866,7 @@ snapshots: buffer-crc32: 1.0.0 readable-stream: 4.7.0 readdir-glob: 1.1.3 - tar-stream: 3.1.8 + tar-stream: 3.2.0 zip-stream: 6.0.1 transitivePeerDependencies: - bare-abort-controller @@ -18386,6 +18908,12 @@ snapshots: dependencies: safer-buffer: 2.1.2 + asn1js@3.0.10: + dependencies: + pvtsutils: 1.3.6 + pvutils: 1.1.5 + tslib: 2.8.1 + assertion-error@2.0.1: {} ast-metadata-inferer@0.8.1: @@ -18423,51 +18951,59 @@ snapshots: dependencies: immediate: 3.3.0 - autoprefixer@10.5.0(postcss@8.5.12): + autoprefixer@10.5.0(postcss@8.5.15): dependencies: browserslist: 4.28.2 - caniuse-lite: 1.0.30001790 + caniuse-lite: 1.0.30001793 fraction.js: 5.3.4 picocolors: 1.1.1 - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 axobject-query@4.1.0: {} - b4a@1.8.0: {} + b4a@1.8.1: {} - babel-loader@9.2.1(@babel/core@7.28.5)(webpack@5.106.2): + babel-loader@9.2.1(@babel/core@7.29.0)(webpack@5.107.0(postcss@8.5.15)): dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 find-cache-dir: 4.0.0 schema-utils: 4.3.3 - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) babel-plugin-dynamic-import-node@2.3.3: dependencies: object.assign: 4.1.7 - babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.28.5): + babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.0): dependencies: - '@babel/compat-data': 7.28.5 - '@babel/core': 7.28.5 - '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.5) + '@babel/compat-data': 7.29.3 + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) semver: 6.3.1 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.28.5): + babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.29.0): dependencies: - '@babel/core': 7.28.5 - '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) core-js-compat: 3.49.0 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-regenerator@0.6.5(@babel/core@7.28.5): + babel-plugin-polyfill-corejs3@0.14.2(@babel/core@7.29.0): dependencies: - '@babel/core': 7.28.5 - '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + core-js-compat: 3.49.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) transitivePeerDependencies: - supports-color @@ -18479,35 +19015,35 @@ snapshots: balanced-match@4.0.4: {} - bare-events@2.8.2: {} + bare-events@2.8.3: {} bare-fs@4.7.1: dependencies: - bare-events: 2.8.2 + bare-events: 2.8.3 bare-path: 3.0.0 - bare-stream: 2.13.0(bare-events@2.8.2) - bare-url: 2.4.2 + bare-stream: 2.13.1(bare-events@2.8.3) + bare-url: 2.4.3 fast-fifo: 1.3.2 transitivePeerDependencies: - bare-abort-controller - react-native-b4a - bare-os@3.9.0: {} + bare-os@3.9.1: {} bare-path@3.0.0: dependencies: - bare-os: 3.9.0 + bare-os: 3.9.1 - bare-stream@2.13.0(bare-events@2.8.2): + bare-stream@2.13.1(bare-events@2.8.3): dependencies: streamx: 2.25.0 teex: 1.0.1 optionalDependencies: - bare-events: 2.8.2 + bare-events: 2.8.3 transitivePeerDependencies: - react-native-b4a - bare-url@2.4.2: + bare-url@2.4.3: dependencies: bare-path: 3.0.0 @@ -18515,7 +19051,7 @@ snapshots: base64id@2.0.0: {} - baseline-browser-mapping@2.10.20: {} + baseline-browser-mapping@2.10.31: {} batch-cluster@17.3.1: {} @@ -18529,25 +19065,23 @@ snapshots: bcrypt@6.0.0: dependencies: - node-addon-api: 8.5.0 - node-gyp: 12.2.0 + node-addon-api: 8.7.0 + node-gyp: 12.3.0 node-gyp-build: 4.8.4 - transitivePeerDependencies: - - supports-color big.js@5.2.2: {} binary-extensions@2.3.0: {} - bits-ui@2.18.0(@internationalized/date@3.12.1)(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2): + bits-ui@2.18.1(@internationalized/date@3.12.1)(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4)): dependencies: '@floating-ui/core': 1.7.5 '@floating-ui/dom': 1.7.6 '@internationalized/date': 3.12.1 esm-env: 1.2.2 - runed: 0.35.1(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2) - svelte: 5.55.2 - svelte-toolbelt: 0.10.6(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2) + runed: 0.35.1(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4)) + svelte: 5.55.8(@typescript-eslint/types@8.59.4) + svelte-toolbelt: 0.10.6(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4)) tabbable: 6.4.0 transitivePeerDependencies: - '@sveltejs/kit' @@ -18558,7 +19092,7 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - body-parser@1.20.4: + body-parser@1.20.5: dependencies: bytes: 3.1.2 content-type: 1.0.5 @@ -18568,7 +19102,7 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.14.1 + qs: 6.15.2 raw-body: 2.5.3 type-is: 1.6.18 unpipe: 1.0.0 @@ -18583,9 +19117,9 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.7.2 on-finished: 2.4.1 - qs: 6.14.1 + qs: 6.15.2 raw-body: 3.0.2 - type-is: 2.0.1 + type-is: 2.1.0 transitivePeerDependencies: - supports-color @@ -18618,7 +19152,7 @@ snapshots: widest-line: 4.0.1 wrap-ansi: 8.1.0 - brace-expansion@1.1.12: + brace-expansion@1.1.14: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 @@ -18627,7 +19161,7 @@ snapshots: dependencies: balanced-match: 1.0.2 - brace-expansion@5.0.5: + brace-expansion@5.0.6: dependencies: balanced-match: 4.0.4 @@ -18637,10 +19171,10 @@ snapshots: browserslist@4.28.2: dependencies: - baseline-browser-mapping: 2.10.20 - caniuse-lite: 1.0.30001790 - electron-to-chromium: 1.5.343 - node-releases: 2.0.38 + baseline-browser-mapping: 2.10.31 + caniuse-lite: 1.0.30001793 + electron-to-chromium: 1.5.360 + node-releases: 2.0.44 update-browserslist-db: 1.2.3(browserslist@4.28.2) buffer-crc32@1.0.0: {} @@ -18662,17 +19196,16 @@ snapshots: buildcheck@0.0.7: optional: true - builtin-modules@5.0.0: {} + builtin-modules@5.2.0: {} - bullmq@5.76.1: + bullmq@5.76.10: dependencies: cron-parser: 4.9.0 ioredis: 5.10.1 - msgpackr: 1.11.5 + msgpackr: 2.0.1 node-abort-controller: 3.1.1 - semver: 7.7.4 + semver: 7.8.0 tslib: 2.8.1 - uuid: 11.1.0 transitivePeerDependencies: - supports-color @@ -18692,32 +19225,20 @@ snapshots: bytes@3.1.2: {} - cac@6.7.14: {} + bytestreamjs@2.0.1: {} - cacache@20.0.3: - dependencies: - '@npmcli/fs': 5.0.0 - fs-minipass: 3.0.3 - glob: 13.0.6 - lru-cache: 11.3.5 - minipass: 7.1.3 - minipass-collect: 2.0.1 - minipass-flush: 1.0.5 - minipass-pipeline: 1.2.4 - p-map: 7.0.4 - ssri: 13.0.1 - unique-filename: 5.0.0 + cac@6.7.14: {} cacheable-lookup@7.0.0: {} cacheable-request@10.2.14: dependencies: - '@types/http-cache-semantics': 4.0.4 + '@types/http-cache-semantics': 4.2.0 get-stream: 6.0.1 http-cache-semantics: 4.2.0 keyv: 4.5.4 mimic-response: 4.0.0 - normalize-url: 8.1.0 + normalize-url: 8.1.1 responselike: 3.0.0 call-bind-apply-helpers@1.0.2: @@ -18725,7 +19246,7 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 - call-bind@1.0.8: + call-bind@1.0.9: dependencies: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 @@ -18755,11 +19276,11 @@ snapshots: caniuse-api@3.0.0: dependencies: browserslist: 4.28.2 - caniuse-lite: 1.0.30001790 + caniuse-lite: 1.0.30001793 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 - caniuse-lite@1.0.30001790: {} + caniuse-lite@1.0.30001793: {} canvas@3.2.3: dependencies: @@ -18769,9 +19290,9 @@ snapshots: ccount@2.0.1: {} - ce-la-react@0.3.2(react@19.2.5): + ce-la-react@0.3.2(react@19.2.6): dependencies: - react: 19.2.5 + react: 19.2.6 chai@5.3.3: dependencies: @@ -18825,20 +19346,6 @@ snapshots: parse5: 7.3.0 parse5-htmlparser2-tree-adapter: 7.1.0 - chevrotain-allstar@0.3.1(chevrotain@11.0.3): - dependencies: - chevrotain: 11.0.3 - lodash-es: 4.18.1 - - chevrotain@11.0.3: - dependencies: - '@chevrotain/cst-dts-gen': 11.0.3 - '@chevrotain/gast': 11.0.3 - '@chevrotain/regexp-to-ast': 11.0.3 - '@chevrotain/types': 11.0.3 - '@chevrotain/utils': 11.0.3 - lodash-es: 4.17.21 - chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -18871,9 +19378,6 @@ snapshots: cjs-module-lexer@2.2.0: {} - class-transformer@0.5.1: - optional: true - clean-css@5.3.3: dependencies: source-map: 0.6.1 @@ -18948,11 +19452,11 @@ snapshots: cluster-key-slot@1.1.2: {} - codemirror-wrapped-line-indent@1.0.9(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.41.1): + codemirror-wrapped-line-indent@1.0.9(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.43.0): dependencies: '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 - '@codemirror/view': 6.41.1 + '@codemirror/view': 6.43.0 collapse-white-space@2.1.0: {} @@ -18998,6 +19502,8 @@ snapshots: commander@8.3.0: {} + commander@9.5.0: {} + comment-json@5.0.0: dependencies: array-timsort: 1.0.3 @@ -19042,18 +19548,16 @@ snapshots: conf@15.1.0: dependencies: - ajv: 8.18.0 - ajv-formats: 3.0.1(ajv@8.18.0) + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) atomically: 2.1.1 debounce-fn: 6.0.0 dot-prop: 10.1.0 env-paths: 3.0.0 json-schema-typed: 8.0.2 - semver: 7.7.4 + semver: 7.8.0 uint8array-extras: 1.5.0 - confbox@0.1.8: {} - config-chain@1.1.13: dependencies: ini: 1.3.8 @@ -19081,8 +19585,12 @@ snapshots: content-disposition@1.0.1: {} + content-disposition@1.1.0: {} + content-type@1.0.5: {} + content-type@2.0.0: {} + convert-source-map@2.0.0: {} cookie-parser@1.4.7: @@ -19111,7 +19619,7 @@ snapshots: copy-text-to-clipboard@3.2.2: {} - copy-webpack-plugin@11.0.0(webpack@5.106.2): + copy-webpack-plugin@11.0.0(webpack@5.107.0(postcss@8.5.15)): dependencies: fast-glob: 3.3.3 glob-parent: 6.0.2 @@ -19119,13 +19627,13 @@ snapshots: normalize-path: 3.0.0 schema-utils: 4.3.3 serialize-javascript: 6.0.2 - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) core-js-compat@3.49.0: dependencies: browserslist: 4.28.2 - core-js@3.47.0: {} + core-js@3.49.0: {} core-util-is@1.0.3: {} @@ -19163,7 +19671,7 @@ snapshots: cpu-features@0.0.10: dependencies: buildcheck: 0.0.7 - nan: 2.26.2 + nan: 2.27.0 optional: true crc-32@1.2.2: {} @@ -19194,50 +19702,50 @@ snapshots: dependencies: type-fest: 1.4.0 - css-blank-pseudo@7.0.1(postcss@8.5.12): + css-blank-pseudo@7.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 - css-declaration-sorter@7.3.0(postcss@8.5.12): + css-declaration-sorter@7.4.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - css-has-pseudo@7.0.3(postcss@8.5.12): + css-has-pseudo@7.0.3(postcss@8.5.15): dependencies: '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.1) - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 - css-loader@6.11.0(webpack@5.106.2): + css-loader@6.11.0(webpack@5.107.0(postcss@8.5.15)): dependencies: - icss-utils: 5.1.0(postcss@8.5.12) - postcss: 8.5.12 - postcss-modules-extract-imports: 3.1.0(postcss@8.5.12) - postcss-modules-local-by-default: 4.2.0(postcss@8.5.12) - postcss-modules-scope: 3.2.1(postcss@8.5.12) - postcss-modules-values: 4.0.0(postcss@8.5.12) + icss-utils: 5.1.0(postcss@8.5.15) + postcss: 8.5.15 + postcss-modules-extract-imports: 3.1.0(postcss@8.5.15) + postcss-modules-local-by-default: 4.2.0(postcss@8.5.15) + postcss-modules-scope: 3.2.1(postcss@8.5.15) + postcss-modules-values: 4.0.0(postcss@8.5.15) postcss-value-parser: 4.2.0 - semver: 7.7.4 + semver: 7.8.0 optionalDependencies: - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) - css-minimizer-webpack-plugin@5.0.1(clean-css@5.3.3)(webpack@5.106.2): + css-minimizer-webpack-plugin@5.0.1(clean-css@5.3.3)(webpack@5.107.0(postcss@8.5.15)): dependencies: '@jridgewell/trace-mapping': 0.3.31 - cssnano: 6.1.2(postcss@8.5.12) + cssnano: 6.1.2(postcss@8.5.15) jest-worker: 29.7.0 - postcss: 8.5.12 + postcss: 8.5.15 schema-utils: 4.3.3 serialize-javascript: 6.0.2 - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) optionalDependencies: clean-css: 5.3.3 - css-prefers-color-scheme@10.0.0(postcss@8.5.12): + css-prefers-color-scheme@10.0.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 css-select@4.3.0: dependencies: @@ -19271,64 +19779,64 @@ snapshots: css.escape@1.5.1: {} - cssdb@8.5.2: {} + cssdb@8.9.0: {} cssesc@3.0.0: {} - cssnano-preset-advanced@6.1.2(postcss@8.5.12): + cssnano-preset-advanced@6.1.2(postcss@8.5.15): dependencies: - autoprefixer: 10.5.0(postcss@8.5.12) + autoprefixer: 10.5.0(postcss@8.5.15) browserslist: 4.28.2 - cssnano-preset-default: 6.1.2(postcss@8.5.12) - postcss: 8.5.12 - postcss-discard-unused: 6.0.5(postcss@8.5.12) - postcss-merge-idents: 6.0.3(postcss@8.5.12) - postcss-reduce-idents: 6.0.3(postcss@8.5.12) - postcss-zindex: 6.0.2(postcss@8.5.12) + cssnano-preset-default: 6.1.2(postcss@8.5.15) + postcss: 8.5.15 + postcss-discard-unused: 6.0.5(postcss@8.5.15) + postcss-merge-idents: 6.0.3(postcss@8.5.15) + postcss-reduce-idents: 6.0.3(postcss@8.5.15) + postcss-zindex: 6.0.2(postcss@8.5.15) - cssnano-preset-default@6.1.2(postcss@8.5.12): + cssnano-preset-default@6.1.2(postcss@8.5.15): dependencies: browserslist: 4.28.2 - css-declaration-sorter: 7.3.0(postcss@8.5.12) - cssnano-utils: 4.0.2(postcss@8.5.12) - postcss: 8.5.12 - postcss-calc: 9.0.1(postcss@8.5.12) - postcss-colormin: 6.1.0(postcss@8.5.12) - postcss-convert-values: 6.1.0(postcss@8.5.12) - postcss-discard-comments: 6.0.2(postcss@8.5.12) - postcss-discard-duplicates: 6.0.3(postcss@8.5.12) - postcss-discard-empty: 6.0.3(postcss@8.5.12) - postcss-discard-overridden: 6.0.2(postcss@8.5.12) - postcss-merge-longhand: 6.0.5(postcss@8.5.12) - postcss-merge-rules: 6.1.1(postcss@8.5.12) - postcss-minify-font-values: 6.1.0(postcss@8.5.12) - postcss-minify-gradients: 6.0.3(postcss@8.5.12) - postcss-minify-params: 6.1.0(postcss@8.5.12) - postcss-minify-selectors: 6.0.4(postcss@8.5.12) - postcss-normalize-charset: 6.0.2(postcss@8.5.12) - postcss-normalize-display-values: 6.0.2(postcss@8.5.12) - postcss-normalize-positions: 6.0.2(postcss@8.5.12) - postcss-normalize-repeat-style: 6.0.2(postcss@8.5.12) - postcss-normalize-string: 6.0.2(postcss@8.5.12) - postcss-normalize-timing-functions: 6.0.2(postcss@8.5.12) - postcss-normalize-unicode: 6.1.0(postcss@8.5.12) - postcss-normalize-url: 6.0.2(postcss@8.5.12) - postcss-normalize-whitespace: 6.0.2(postcss@8.5.12) - postcss-ordered-values: 6.0.2(postcss@8.5.12) - postcss-reduce-initial: 6.1.0(postcss@8.5.12) - postcss-reduce-transforms: 6.0.2(postcss@8.5.12) - postcss-svgo: 6.0.3(postcss@8.5.12) - postcss-unique-selectors: 6.0.4(postcss@8.5.12) + css-declaration-sorter: 7.4.0(postcss@8.5.15) + cssnano-utils: 4.0.2(postcss@8.5.15) + postcss: 8.5.15 + postcss-calc: 9.0.1(postcss@8.5.15) + postcss-colormin: 6.1.0(postcss@8.5.15) + postcss-convert-values: 6.1.0(postcss@8.5.15) + postcss-discard-comments: 6.0.2(postcss@8.5.15) + postcss-discard-duplicates: 6.0.3(postcss@8.5.15) + postcss-discard-empty: 6.0.3(postcss@8.5.15) + postcss-discard-overridden: 6.0.2(postcss@8.5.15) + postcss-merge-longhand: 6.0.5(postcss@8.5.15) + postcss-merge-rules: 6.1.1(postcss@8.5.15) + postcss-minify-font-values: 6.1.0(postcss@8.5.15) + postcss-minify-gradients: 6.0.3(postcss@8.5.15) + postcss-minify-params: 6.1.0(postcss@8.5.15) + postcss-minify-selectors: 6.0.4(postcss@8.5.15) + postcss-normalize-charset: 6.0.2(postcss@8.5.15) + postcss-normalize-display-values: 6.0.2(postcss@8.5.15) + postcss-normalize-positions: 6.0.2(postcss@8.5.15) + postcss-normalize-repeat-style: 6.0.2(postcss@8.5.15) + postcss-normalize-string: 6.0.2(postcss@8.5.15) + postcss-normalize-timing-functions: 6.0.2(postcss@8.5.15) + postcss-normalize-unicode: 6.1.0(postcss@8.5.15) + postcss-normalize-url: 6.0.2(postcss@8.5.15) + postcss-normalize-whitespace: 6.0.2(postcss@8.5.15) + postcss-ordered-values: 6.0.2(postcss@8.5.15) + postcss-reduce-initial: 6.1.0(postcss@8.5.15) + postcss-reduce-transforms: 6.0.2(postcss@8.5.15) + postcss-svgo: 6.0.3(postcss@8.5.15) + postcss-unique-selectors: 6.0.4(postcss@8.5.15) - cssnano-utils@4.0.2(postcss@8.5.12): + cssnano-utils@4.0.2(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - cssnano@6.1.2(postcss@8.5.12): + cssnano@6.1.2(postcss@8.5.15): dependencies: - cssnano-preset-default: 6.1.2(postcss@8.5.12) + cssnano-preset-default: 6.1.2(postcss@8.5.15) lilconfig: 3.1.3 - postcss: 8.5.12 + postcss: 8.5.15 csso@5.0.5: dependencies: @@ -19342,17 +19850,17 @@ snapshots: csstype@3.2.3: {} - cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.1): + cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.4): dependencies: cose-base: 1.0.3 - cytoscape: 3.33.1 + cytoscape: 3.33.4 - cytoscape-fcose@2.2.0(cytoscape@3.33.1): + cytoscape-fcose@2.2.0(cytoscape@3.33.4): dependencies: cose-base: 2.2.0 - cytoscape: 3.33.1 + cytoscape: 3.33.4 - cytoscape@3.33.1: {} + cytoscape@3.33.4: {} d3-array@2.12.1: dependencies: @@ -19384,7 +19892,7 @@ snapshots: d3-delaunay@6.0.4: dependencies: - delaunator: 5.0.1 + delaunator: 5.1.0 d3-dispatch@3.0.1: {} @@ -19411,7 +19919,7 @@ snapshots: d3-quadtree: 3.0.1 d3-timer: 3.0.1 - d3-format@3.1.0: {} + d3-format@3.1.2: {} d3-geo@3.1.1: dependencies: @@ -19446,7 +19954,7 @@ snapshots: d3-scale@4.0.2: dependencies: d3-array: 3.2.4 - d3-format: 3.1.0 + d3-format: 3.1.2 d3-interpolate: 3.0.1 d3-time: 3.1.0 d3-time-format: 4.1.0 @@ -19503,7 +20011,7 @@ snapshots: d3-ease: 3.0.1 d3-fetch: 3.0.1 d3-force: 3.0.0 - d3-format: 3.1.0 + d3-format: 3.1.2 d3-geo: 3.1.1 d3-hierarchy: 3.1.2 d3-interpolate: 3.0.1 @@ -19526,7 +20034,7 @@ snapshots: es5-ext: 0.10.64 type: 2.7.3 - dagre-d3-es@7.0.13: + dagre-d3-es@7.0.14: dependencies: d3: 7.9.0 lodash-es: 4.18.1 @@ -19537,7 +20045,7 @@ snapshots: whatwg-url: 14.2.0 optional: true - dayjs@1.11.19: {} + dayjs@1.11.20: {} debounce-fn@6.0.0: dependencies: @@ -19563,7 +20071,7 @@ snapshots: decimal.js@10.6.0: {} - decode-named-character-reference@1.2.0: + decode-named-character-reference@1.3.0: dependencies: character-entities: 2.0.2 @@ -19583,7 +20091,7 @@ snapshots: default-browser-id@5.0.1: {} - default-browser@5.4.0: + default-browser@5.5.0: dependencies: bundle-name: 4.1.0 default-browser-id: 5.0.1 @@ -19610,7 +20118,7 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 - delaunator@5.0.1: + delaunator@5.1.0: dependencies: robust-predicates: 3.0.3 @@ -19641,7 +20149,7 @@ snapshots: transitivePeerDependencies: - supports-color - devalue@5.7.1: {} + devalue@5.8.1: {} devlop@1.1.0: dependencies: @@ -19676,7 +20184,7 @@ snapshots: docker-compose@1.4.2: dependencies: - yaml: 2.8.3 + yaml: 2.9.0 docker-modem@5.0.7: dependencies: @@ -19687,21 +20195,21 @@ snapshots: transitivePeerDependencies: - supports-color - dockerode@4.0.10: + dockerode@4.0.12: dependencies: '@balena/dockerignore': 1.0.2 '@grpc/grpc-js': 1.14.3 '@grpc/proto-loader': 0.7.15 docker-modem: 5.0.7 - protobufjs: 7.5.5 + protobufjs: 7.6.0 tar-fs: 2.1.4 uuid: 10.0.0 transitivePeerDependencies: - supports-color - docusaurus-lunr-search@3.6.0(@docusaurus/core@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5): + docusaurus-lunr-search@3.6.0(@docusaurus/core@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) autocomplete.js: 0.37.1 clsx: 2.1.1 gauge: 3.0.2 @@ -19709,11 +20217,11 @@ snapshots: hast-util-to-text: 2.0.1 hogan.js: 3.0.2 lunr: 2.3.9 - lunr-languages: 1.14.0 + lunr-languages: 1.20.0 mark.js: 8.11.1 minimatch: 3.1.5 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) rehype-parse: 7.0.1 to-vfile: 6.1.0 unified: 9.2.2 @@ -19751,7 +20259,7 @@ snapshots: dependencies: domelementtype: 2.3.0 - dompurify@3.3.1: + dompurify@3.4.5: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -19800,7 +20308,7 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.343: {} + electron-to-chromium@1.5.360: {} emoji-regex@10.6.0: {} @@ -19816,21 +20324,16 @@ snapshots: encodeurl@2.0.0: {} - encoding@0.1.13: - dependencies: - iconv-lite: 0.6.3 - optional: true - end-of-stream@1.4.5: dependencies: once: 1.4.0 - engine.io-client@6.6.4: + engine.io-client@6.6.5: dependencies: '@socket.io/component-emitter': 3.1.2 debug: 4.4.3 engine.io-parser: 5.2.3 - ws: 8.18.3 + ws: 8.20.1 xmlhttprequest-ssl: 2.1.2 transitivePeerDependencies: - bufferutil @@ -19839,23 +20342,24 @@ snapshots: engine.io-parser@5.2.3: {} - engine.io@6.6.5: + engine.io@6.6.8: dependencies: '@types/cors': 2.8.19 - '@types/node': 24.12.2 + '@types/node': 24.12.4 + '@types/ws': 8.18.1 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.7.2 cors: 2.8.6 debug: 4.4.3 engine.io-parser: 5.2.3 - ws: 8.18.3 + ws: 8.20.1 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - enhanced-resolve@5.21.0: + enhanced-resolve@5.21.5: dependencies: graceful-fs: 4.2.11 tapable: 2.3.3 @@ -19872,8 +20376,6 @@ snapshots: env-paths@3.0.0: {} - err-code@2.0.3: {} - error-ex@1.3.4: dependencies: is-arrayish: 0.2.1 @@ -19895,7 +20397,9 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 - hasown: 2.0.2 + hasown: 2.0.3 + + es-toolkit@1.46.1: {} es5-ext@0.10.64: dependencies: @@ -19991,6 +20495,35 @@ snapshots: '@esbuild/win32-ia32': 0.27.4 '@esbuild/win32-x64': 0.27.4 + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + esbuild@0.28.0: optionalDependencies: '@esbuild/aix-ppc64': 0.28.0 @@ -20032,84 +20565,84 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-config-prettier@10.1.8(eslint@10.2.1(jiti@2.6.1)): + eslint-config-prettier@10.1.8(eslint@10.4.0(jiti@2.7.0)): dependencies: - eslint: 10.2.1(jiti@2.6.1) + eslint: 10.4.0(jiti@2.7.0) - eslint-plugin-better-tailwindcss@4.5.0(eslint@10.2.1(jiti@2.6.1))(tailwindcss@4.2.4)(typescript@6.0.3): + eslint-plugin-better-tailwindcss@4.5.0(eslint@10.4.0(jiti@2.7.0))(tailwindcss@4.3.0)(typescript@6.0.3): dependencies: - '@eslint/css-tree': 4.0.2 - '@valibot/to-json-schema': 1.6.0(valibot@1.3.1(typescript@6.0.3)) - enhanced-resolve: 5.21.0 - jiti: 2.6.1 + '@eslint/css-tree': 4.0.3 + '@valibot/to-json-schema': 1.7.0(valibot@1.4.0(typescript@6.0.3)) + enhanced-resolve: 5.21.5 + jiti: 2.7.0 synckit: 0.11.12 tailwind-csstree: 0.3.1 - tailwindcss: 4.2.4 + tailwindcss: 4.3.0 tsconfig-paths-webpack-plugin: 4.2.0 - valibot: 1.3.1(typescript@6.0.3) + valibot: 1.4.0(typescript@6.0.3) optionalDependencies: - eslint: 10.2.1(jiti@2.6.1) + eslint: 10.4.0(jiti@2.7.0) transitivePeerDependencies: - '@eslint/css' - typescript - eslint-plugin-compat@7.0.1(eslint@10.2.1(jiti@2.6.1)): + eslint-plugin-compat@7.0.2(eslint@10.4.0(jiti@2.7.0)): dependencies: '@mdn/browser-compat-data': 6.1.5 ast-metadata-inferer: 0.8.1 browserslist: 4.28.2 - eslint: 10.2.1(jiti@2.6.1) + eslint: 10.4.0(jiti@2.7.0) find-up: 5.0.0 globals: 15.15.0 lodash.memoize: 4.1.2 - semver: 7.7.4 + semver: 7.8.0 - eslint-plugin-prettier@5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.2.1(jiti@2.6.1)))(eslint@10.2.1(jiti@2.6.1))(prettier@3.8.3): + eslint-plugin-prettier@5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.4.0(jiti@2.7.0)))(eslint@10.4.0(jiti@2.7.0))(prettier@3.8.3): dependencies: - eslint: 10.2.1(jiti@2.6.1) + eslint: 10.4.0(jiti@2.7.0) prettier: 3.8.3 prettier-linter-helpers: 1.0.1 synckit: 0.11.12 optionalDependencies: '@types/eslint': 9.6.1 - eslint-config-prettier: 10.1.8(eslint@10.2.1(jiti@2.6.1)) + eslint-config-prettier: 10.1.8(eslint@10.4.0(jiti@2.7.0)) - eslint-plugin-svelte@3.17.1(eslint@10.2.1(jiti@2.6.1))(svelte@5.55.2): + eslint-plugin-svelte@3.17.1(eslint@10.4.0(jiti@2.7.0))(svelte@5.55.8(@typescript-eslint/types@8.59.4)): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) '@jridgewell/sourcemap-codec': 1.5.5 - eslint: 10.2.1(jiti@2.6.1) + eslint: 10.4.0(jiti@2.7.0) esutils: 2.0.3 globals: 16.5.0 known-css-properties: 0.37.0 - postcss: 8.5.12 - postcss-load-config: 3.1.4(postcss@8.5.12) - postcss-safe-parser: 7.0.1(postcss@8.5.12) - semver: 7.7.4 - svelte-eslint-parser: 1.6.0(svelte@5.55.2) + postcss: 8.5.15 + postcss-load-config: 3.1.4(postcss@8.5.15) + postcss-safe-parser: 7.0.1(postcss@8.5.15) + semver: 7.8.0 + svelte-eslint-parser: 1.6.1(svelte@5.55.8(@typescript-eslint/types@8.59.4)) optionalDependencies: - svelte: 5.55.2 + svelte: 5.55.8(@typescript-eslint/types@8.59.4) transitivePeerDependencies: - ts-node - eslint-plugin-unicorn@64.0.0(eslint@10.2.1(jiti@2.6.1)): + eslint-plugin-unicorn@64.0.0(eslint@10.4.0(jiti@2.7.0)): dependencies: '@babel/helper-validator-identifier': 7.28.5 - '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) change-case: 5.4.4 ci-info: 4.4.0 clean-regexp: 1.0.0 core-js-compat: 3.49.0 - eslint: 10.2.1(jiti@2.6.1) + eslint: 10.4.0(jiti@2.7.0) find-up-simple: 1.0.1 - globals: 17.5.0 + globals: 17.6.0 indent-string: 5.0.0 is-builtin-module: 5.0.0 jsesc: 3.1.0 pluralize: 8.0.0 regexp-tree: 0.1.27 - regjsparser: 0.13.0 - semver: 7.7.4 + regjsparser: 0.13.1 + semver: 7.8.0 strip-indent: 4.1.1 eslint-scope@5.1.1: @@ -20125,7 +20658,7 @@ snapshots: eslint-scope@9.1.2: dependencies: '@types/esrecurse': 4.3.1 - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 esrecurse: 4.3.0 estraverse: 5.3.0 @@ -20135,19 +20668,19 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.2.1(jiti@2.6.1): + eslint@10.4.0(jiti@2.7.0): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.23.5 - '@eslint/config-helpers': 0.5.5 + '@eslint/config-helpers': 0.6.0 '@eslint/core': 1.2.1 '@eslint/plugin-kit': 0.7.1 '@humanfs/node': 0.16.8 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 - '@types/estree': 1.0.8 - ajv: 6.14.0 + '@types/estree': 1.0.9 + ajv: 6.15.0 cross-spawn: 7.0.6 debug: 4.4.3 escape-string-regexp: 4.0.0 @@ -20168,7 +20701,7 @@ snapshots: natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: - jiti: 2.6.1 + jiti: 2.7.0 transitivePeerDependencies: - supports-color @@ -20199,10 +20732,11 @@ snapshots: dependencies: estraverse: 5.3.0 - esrap@2.2.4: + esrap@2.2.9(@typescript-eslint/types@8.59.4): dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - '@typescript-eslint/types': 8.59.0 + optionalDependencies: + '@typescript-eslint/types': 8.59.4 esrecurse@4.3.0: dependencies: @@ -20214,7 +20748,7 @@ snapshots: estree-util-attach-comments@3.0.0: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 estree-util-build-jsx@3.0.1: dependencies: @@ -20227,7 +20761,7 @@ snapshots: estree-util-scope@1.0.0: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 devlop: 1.1.0 estree-util-to-js@2.0.0: @@ -20238,7 +20772,7 @@ snapshots: estree-util-value-to-estree@3.5.0: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 estree-util-visit@2.0.0: dependencies: @@ -20249,19 +20783,19 @@ snapshots: estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 esutils@2.0.3: {} eta@2.2.0: {} - eta@4.5.1: {} + eta@4.6.0: {} etag@1.8.1: {} eval@0.1.8: dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 require-like: 0.1.2 event-emitter@0.3.5: @@ -20275,14 +20809,12 @@ snapshots: events-universal@1.0.1: dependencies: - bare-events: 2.8.2 + bare-events: 2.8.3 transitivePeerDependencies: - bare-abort-controller events@3.3.0: {} - eventsource-parser@3.0.6: {} - execa@5.1.1: dependencies: cross-spawn: 7.0.6 @@ -20318,11 +20850,11 @@ snapshots: exponential-backoff@3.1.3: {} - express@4.22.1: + express@4.22.2: dependencies: accepts: 1.3.8 array-flatten: 1.1.1 - body-parser: 1.20.4 + body-parser: 1.20.5 content-disposition: 0.5.4 content-type: 1.0.5 cookie: 0.7.2 @@ -20341,7 +20873,7 @@ snapshots: parseurl: 1.3.3 path-to-regexp: 0.1.13 proxy-addr: 2.0.7 - qs: 6.14.1 + qs: 6.15.2 range-parser: 1.2.1 safe-buffer: 5.2.1 send: 0.19.2 @@ -20358,7 +20890,7 @@ snapshots: dependencies: accepts: 2.0.0 body-parser: 2.2.2 - content-disposition: 1.0.1 + content-disposition: 1.1.0 content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.2.2 @@ -20376,13 +20908,13 @@ snapshots: once: 1.4.0 parseurl: 1.3.3 proxy-addr: 2.0.7 - qs: 6.14.1 + qs: 6.15.2 range-parser: 1.2.1 router: 2.2.0 send: 1.2.1 serve-static: 2.2.1 statuses: 2.0.2 - type-is: 2.0.1 + type-is: 2.1.0 vary: 1.1.2 transitivePeerDependencies: - supports-color @@ -20397,7 +20929,7 @@ snapshots: extend@3.0.2: {} - fabric@7.3.1: + fabric@7.4.0: optionalDependencies: canvas: 3.2.3 jsdom: 26.1.0(canvas@3.2.3) @@ -20431,7 +20963,7 @@ snapshots: fast-safe-stringify@2.1.1: {} - fast-uri@3.1.0: {} + fast-uri@3.1.2: {} fastq@1.20.1: dependencies: @@ -20453,7 +20985,7 @@ snapshots: dependencies: xml-js: 1.6.11 - fflate@0.8.2: {} + fflate@0.8.3: {} figures@3.2.0: dependencies: @@ -20463,11 +20995,11 @@ snapshots: dependencies: flat-cache: 4.0.1 - file-loader@6.2.0(webpack@5.106.2): + file-loader@6.2.0(webpack@5.107.0(postcss@8.5.15)): dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) file-source@0.6.1: dependencies: @@ -20545,14 +21077,14 @@ snapshots: async: 0.2.10 which: 1.3.1 - follow-redirects@1.15.11: {} + follow-redirects@1.16.0: {} foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 signal-exit: 4.1.0 - fork-ts-checker-webpack-plugin@9.1.0(typescript@5.9.3)(webpack@5.106.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0)): + fork-ts-checker-webpack-plugin@9.1.0(typescript@5.9.3)(webpack@5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.21))(esbuild@0.28.0)(lightningcss@1.32.0)): dependencies: '@babel/code-frame': 7.29.0 chalk: 4.1.2 @@ -20564,10 +21096,10 @@ snapshots: minimatch: 3.1.5 node-abort-controller: 3.1.1 schema-utils: 3.3.0 - semver: 7.7.4 + semver: 7.8.0 tapable: 2.3.3 typescript: 5.9.3 - webpack: 5.106.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0) + webpack: 5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.21))(esbuild@0.28.0)(lightningcss@1.32.0) form-data-encoder@2.1.4: {} @@ -20576,7 +21108,7 @@ snapshots: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 - hasown: 2.0.2 + hasown: 2.0.3 mime-types: 2.1.35 format@0.2.2: {} @@ -20605,7 +21137,7 @@ snapshots: jsonfile: 6.2.1 universalify: 2.0.1 - fs-extra@11.3.4: + fs-extra@11.3.5: dependencies: graceful-fs: 4.2.11 jsonfile: 6.2.1 @@ -20615,10 +21147,6 @@ snapshots: dependencies: minipass: 3.3.6 - fs-minipass@3.0.3: - dependencies: - minipass: 7.1.3 - fs-monkey@1.1.0: {} fs.realpath@1.0.0: {} @@ -20643,14 +21171,16 @@ snapshots: strip-ansi: 6.0.1 wide-align: 1.1.5 + generic-pool@3.9.0: {} + gensync@1.0.0-beta.2: {} geo-coordinates-parser@1.7.4: {} - geo-tz@8.1.6: + geo-tz@8.1.7: dependencies: - '@turf/boolean-point-in-polygon': 7.3.4 - '@turf/helpers': 7.3.4 + '@turf/boolean-point-in-polygon': 7.3.5 + '@turf/helpers': 7.3.5 geobuf: 3.0.2 pbf: 3.3.0 @@ -20664,7 +21194,7 @@ snapshots: get-caller-file@2.0.5: {} - get-east-asian-width@1.5.0: {} + get-east-asian-width@1.6.0: {} get-intrinsic@1.3.0: dependencies: @@ -20676,7 +21206,7 @@ snapshots: get-proto: 1.0.1 gopd: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.2 + hasown: 2.0.3 math-intrinsics: 1.1.0 get-own-enumerable-property-symbols@3.0.2: {} @@ -20690,7 +21220,7 @@ snapshots: get-stream@6.0.1: {} - get-tsconfig@4.13.0: + get-tsconfig@4.14.0: dependencies: resolve-pkg-maps: 1.0.0 @@ -20749,7 +21279,7 @@ snapshots: globals@16.5.0: {} - globals@17.5.0: {} + globals@17.6.0: {} globalyzer@0.1.0: {} @@ -20820,12 +21350,12 @@ snapshots: happy-dom@20.9.0: dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/whatwg-mimetype': 3.0.2 '@types/ws': 8.18.1 entities: 7.0.1 whatwg-mimetype: 3.0.0 - ws: 8.20.0 + ws: 8.20.1 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -20846,7 +21376,7 @@ snapshots: has-yarn@3.0.0: {} - hasown@2.0.2: + hasown@2.0.3: dependencies: function-bind: 1.1.2 @@ -20884,14 +21414,14 @@ snapshots: dependencies: '@types/hast': 3.0.4 '@types/unist': 3.0.3 - '@ungap/structured-clone': 1.3.0 + '@ungap/structured-clone': 1.3.1 hast-util-from-parse5: 8.0.3 hast-util-to-parse5: 8.0.1 html-void-elements: 3.0.0 mdast-util-to-hast: 13.2.1 parse5: 7.3.0 unist-util-position: 5.0.0 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 vfile: 6.0.3 web-namespaces: 2.0.1 zwitch: 2.0.4 @@ -20915,7 +21445,7 @@ snapshots: hast-util-to-estree@3.1.3: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/estree-jsx': 1.0.5 '@types/hast': 3.0.4 comma-separated-tokens: 2.0.3 @@ -20936,7 +21466,7 @@ snapshots: hast-util-to-jsx-runtime@2.3.6: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/hast': 3.0.4 '@types/unist': 3.0.3 comma-separated-tokens: 2.0.3 @@ -21040,7 +21570,7 @@ snapshots: he: 1.2.0 param-case: 3.0.4 relateurl: 0.2.7 - terser: 5.46.1 + terser: 5.47.1 html-minifier-terser@7.2.0: dependencies: @@ -21050,7 +21580,7 @@ snapshots: entities: 4.5.0 param-case: 3.0.4 relateurl: 0.2.7 - terser: 5.46.1 + terser: 5.47.1 html-tags@3.3.1: {} @@ -21064,7 +21594,7 @@ snapshots: html-void-elements@3.0.0: {} - html-webpack-plugin@5.6.5(webpack@5.106.2): + html-webpack-plugin@5.6.7(webpack@5.107.0(postcss@8.5.15)): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -21072,7 +21602,7 @@ snapshots: pretty-error: 4.0.0 tapable: 2.3.3 optionalDependencies: - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) htmlparser2@6.1.0: dependencies: @@ -21097,13 +21627,6 @@ snapshots: http-deceiver@1.2.7: {} - http-errors@1.6.3: - dependencies: - depd: 1.1.2 - inherits: 2.0.3 - setprototypeof: 1.1.0 - statuses: 1.5.0 - http-errors@1.8.1: dependencies: depd: 1.1.2 @@ -21128,6 +21651,7 @@ snapshots: debug: 4.4.3 transitivePeerDependencies: - supports-color + optional: true http-proxy-middleware@2.0.9(@types/express@4.17.25): dependencies: @@ -21144,7 +21668,7 @@ snapshots: http-proxy@1.18.1: dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.15.11 + follow-redirects: 1.16.0 requires-port: 1.0.0 transitivePeerDependencies: - debug @@ -21167,6 +21691,7 @@ snapshots: debug: 4.4.3 transitivePeerDependencies: - supports-color + optional: true human-signals@2.1.0: {} @@ -21188,9 +21713,9 @@ snapshots: dependencies: safer-buffer: 2.1.2 - icss-utils@5.1.0(postcss@8.5.12): + icss-utils@5.1.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 ieee754@1.2.1: {} @@ -21213,7 +21738,7 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 - import-in-the-middle@3.0.0: + import-in-the-middle@3.0.1: dependencies: acorn: 8.16.0 acorn-import-attributes: 1.9.5(acorn@8.16.0) @@ -21222,6 +21747,8 @@ snapshots: import-lazy@4.0.0: {} + import-meta-resolve@4.2.0: {} + imurmurhash@0.1.4: {} indent-string@4.0.0: {} @@ -21235,8 +21762,6 @@ snapshots: once: 1.4.0 wrappy: 1.0.2 - inherits@2.0.3: {} - inherits@2.0.4: {} ini@1.3.8: {} @@ -21245,9 +21770,9 @@ snapshots: inline-style-parser@0.2.7: {} - inquirer@8.2.7(@types/node@24.12.2): + inquirer@8.2.7(@types/node@24.12.4): dependencies: - '@inquirer/external-editor': 1.0.3(@types/node@24.12.2) + '@inquirer/external-editor': 1.0.3(@types/node@24.12.4) ansi-escapes: 4.3.2 chalk: 4.1.2 cli-cursor: 3.1.0 @@ -21276,10 +21801,10 @@ snapshots: '@formatjs/icu-messageformat-parser': 2.11.4 tslib: 2.8.1 - intl-messageformat@11.2.1: + intl-messageformat@11.2.6: dependencies: - '@formatjs/fast-memoize': 3.1.2 - '@formatjs/icu-messageformat-parser': 3.5.4 + '@formatjs/fast-memoize': 3.1.5 + '@formatjs/icu-messageformat-parser': 3.5.9 invariant@2.2.4: dependencies: @@ -21299,11 +21824,9 @@ snapshots: transitivePeerDependencies: - supports-color - ip-address@10.1.0: {} - ipaddr.js@1.9.1: {} - ipaddr.js@2.3.0: {} + ipaddr.js@2.4.0: {} is-alphabetical@2.0.1: {} @@ -21322,15 +21845,15 @@ snapshots: is-builtin-module@5.0.0: dependencies: - builtin-modules: 5.0.0 + builtin-modules: 5.2.0 is-ci@3.0.1: dependencies: ci-info: 3.9.0 - is-core-module@2.16.1: + is-core-module@2.16.2: dependencies: - hasown: 2.0.2 + hasown: 2.0.3 is-decimal@2.0.1: {} @@ -21365,7 +21888,7 @@ snapshots: is-interactive@2.0.0: {} - is-network-error@1.3.0: {} + is-network-error@1.3.2: {} is-npm@6.1.0: {} @@ -21396,7 +21919,7 @@ snapshots: is-reference@3.0.3: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 is-regexp@1.0.0: {} @@ -21466,7 +21989,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 24.12.2 + '@types/node': 24.12.4 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -21474,13 +21997,13 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 merge-stream: 2.0.0 supports-color: 8.1.1 jest-worker@29.7.0: dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -21489,7 +22012,7 @@ snapshots: jiti@2.4.2: {} - jiti@2.6.1: {} + jiti@2.7.0: {} jmespath@0.16.0: {} @@ -21501,7 +22024,7 @@ snapshots: '@sideway/formula': 3.0.1 '@sideway/pinpoint': 2.0.0 - jose@6.2.2: {} + jose@6.2.3: {} js-tokens@10.0.0: {} @@ -21538,7 +22061,7 @@ snapshots: whatwg-encoding: 3.1.1 whatwg-mimetype: 4.0.0 whatwg-url: 14.2.0 - ws: 8.20.0 + ws: 8.20.1 xml-name-validator: 5.0.0 optionalDependencies: canvas: 3.2.3 @@ -21562,8 +22085,6 @@ snapshots: json-schema-typed@8.0.2: {} - json-schema@0.4.0: {} - json-source-map@0.6.1: {} json-stable-stringify-without-jsonify@1.0.1: {} @@ -21599,7 +22120,7 @@ snapshots: lodash.isstring: 4.0.1 lodash.once: 4.1.1 ms: 2.1.3 - semver: 7.7.4 + semver: 7.8.0 just-compare@2.3.0: {} @@ -21616,11 +22137,11 @@ snapshots: jwa: 2.0.1 safe-buffer: 5.2.1 - katex@0.16.27: + katex@0.16.47: dependencies: commander: 8.3.0 - kdbush@4.0.2: {} + kdbush@4.1.0: {} keygrip@1.1.0: dependencies: @@ -21660,7 +22181,7 @@ snapshots: on-finished: 2.4.1 parseurl: 1.3.3 statuses: 2.0.2 - type-is: 2.0.1 + type-is: 2.1.0 vary: 1.1.2 kysely-postgres-js@3.0.0(kysely@0.28.17)(postgres@3.4.9): @@ -21671,19 +22192,11 @@ snapshots: kysely@0.28.17: {} - langium@3.3.1: - dependencies: - chevrotain: 11.0.3 - chevrotain-allstar: 0.3.1(chevrotain@11.0.3) - vscode-languageserver: 9.0.1 - vscode-languageserver-textdocument: 1.0.12 - vscode-uri: 3.0.8 - latest-version@7.0.0: dependencies: package-json: 8.1.1 - launch-editor@2.12.0: + launch-editor@2.13.2: dependencies: picocolors: 1.1.1 shell-quote: 1.8.3 @@ -21764,7 +22277,7 @@ snapshots: load-tsconfig@0.2.5: {} - loader-runner@4.3.1: {} + loader-runner@4.3.2: {} loader-utils@2.0.4: dependencies: @@ -21786,8 +22299,6 @@ snapshots: dependencies: p-locate: 6.0.0 - lodash-es@4.17.21: {} - lodash-es@4.18.1: {} lodash.camelcase@4.3.0: {} @@ -21851,7 +22362,7 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@11.3.5: {} + lru-cache@11.5.0: {} lru-cache@5.1.1: dependencies: @@ -21861,7 +22372,7 @@ snapshots: dependencies: es5-ext: 0.10.64 - lunr-languages@1.14.0: {} + lunr-languages@1.20.0: {} lunr@2.3.9: {} @@ -21879,13 +22390,13 @@ snapshots: magicast@0.3.5: dependencies: - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/types': 7.29.0 source-map-js: 1.2.1 - magicast@0.5.2: + magicast@0.5.3: dependencies: - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/types': 7.29.0 source-map-js: 1.2.1 @@ -21895,40 +22406,24 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.7.4 - - make-fetch-happen@15.0.3: - dependencies: - '@npmcli/agent': 4.0.0 - cacache: 20.0.3 - http-cache-semantics: 4.2.0 - minipass: 7.1.3 - minipass-fetch: 5.0.1 - minipass-flush: 1.0.5 - minipass-pipeline: 1.2.4 - negotiator: 1.0.0 - proc-log: 6.1.0 - promise-retry: 2.0.1 - ssri: 13.0.1 - transitivePeerDependencies: - - supports-color + semver: 7.8.0 maplibre-gl@5.24.0: dependencies: '@mapbox/jsonlint-lines-primitives': 2.0.2 '@mapbox/point-geometry': 1.1.0 - '@mapbox/tiny-sdf': 2.1.0 + '@mapbox/tiny-sdf': 2.2.0 '@mapbox/unitbezier': 0.0.1 '@mapbox/vector-tile': 2.0.4 '@mapbox/whoots-js': 3.1.0 '@maplibre/geojson-vt': 6.1.0 - '@maplibre/maplibre-gl-style-spec': 24.8.1 + '@maplibre/maplibre-gl-style-spec': 24.8.5 '@maplibre/mlt': 1.1.9 '@maplibre/vt-pbf': 4.3.0 '@types/geojson': 7946.0.16 earcut: 3.0.2 gl-matrix: 3.4.4 - kdbush: 4.0.2 + kdbush: 4.1.0 murmurhash-js: 1.0.0 pbf: 4.0.1 potpack: 2.1.0 @@ -21953,7 +22448,7 @@ snapshots: '@types/unist': 3.0.3 ccount: 2.0.1 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 parse-entities: 4.0.2 stringify-entities: 4.0.4 @@ -21968,11 +22463,11 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 - mdast-util-from-markdown@2.0.2: + mdast-util-from-markdown@2.0.3: dependencies: '@types/mdast': 4.0.4 '@types/unist': 3.0.3 - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 devlop: 1.1.0 mdast-util-to-string: 4.0.0 micromark: 4.0.2 @@ -21990,7 +22485,7 @@ snapshots: '@types/mdast': 4.0.4 devlop: 1.1.0 escape-string-regexp: 5.0.0 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 micromark-extension-frontmatter: 2.0.0 transitivePeerDependencies: @@ -22008,7 +22503,7 @@ snapshots: dependencies: '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 micromark-util-normalize-identifier: 2.0.1 transitivePeerDependencies: @@ -22017,7 +22512,7 @@ snapshots: mdast-util-gfm-strikethrough@2.0.0: dependencies: '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -22027,7 +22522,7 @@ snapshots: '@types/mdast': 4.0.4 devlop: 1.1.0 markdown-table: 3.0.4 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -22036,14 +22531,14 @@ snapshots: dependencies: '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color mdast-util-gfm@3.1.0: dependencies: - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-gfm-autolink-literal: 2.0.1 mdast-util-gfm-footnote: 2.1.0 mdast-util-gfm-strikethrough: 2.0.0 @@ -22059,7 +22554,7 @@ snapshots: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -22072,7 +22567,7 @@ snapshots: '@types/unist': 3.0.3 ccount: 2.0.1 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 parse-entities: 4.0.2 stringify-entities: 4.0.4 @@ -22083,7 +22578,7 @@ snapshots: mdast-util-mdx@3.0.0: dependencies: - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-mdx-expression: 2.0.1 mdast-util-mdx-jsx: 3.2.0 mdast-util-mdxjs-esm: 2.0.1 @@ -22097,7 +22592,7 @@ snapshots: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -22111,12 +22606,12 @@ snapshots: dependencies: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 - '@ungap/structured-clone': 1.3.0 + '@ungap/structured-clone': 1.3.1 devlop: 1.1.0 micromark-util-sanitize-uri: 2.0.1 trim-lines: 3.0.1 unist-util-position: 5.0.0 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 vfile: 6.0.3 mdast-util-to-markdown@2.1.2: @@ -22128,7 +22623,7 @@ snapshots: mdast-util-to-string: 4.0.0 micromark-util-classify-character: 2.0.1 micromark-util-decode-string: 2.0.1 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 zwitch: 2.0.4 mdast-util-to-string@4.0.0: @@ -22139,11 +22634,11 @@ snapshots: mdn-data@2.0.30: {} - mdn-data@2.27.1: {} + mdn-data@2.28.0: {} - media-chrome@4.19.0(react@19.2.5): + media-chrome@4.19.0(react@19.2.6): dependencies: - ce-la-react: 0.3.2(react@19.2.5) + ce-la-react: 0.3.2(react@19.2.6) transitivePeerDependencies: - react @@ -22168,7 +22663,7 @@ snapshots: '@jsonjoy.com/json-pack': 1.21.0(tslib@2.8.1) '@jsonjoy.com/util': 1.9.0(tslib@2.8.1) glob-to-regex.js: 1.2.0(tslib@2.8.1) - thingies: 2.5.0(tslib@2.8.1) + thingies: 2.6.0(tslib@2.8.1) tree-dump: 1.1.0(tslib@2.8.1) tslib: 2.8.1 @@ -22193,34 +22688,35 @@ snapshots: merge2@1.4.1: {} - mermaid@11.12.2: + mermaid@11.15.0: dependencies: - '@braintree/sanitize-url': 7.1.1 - '@iconify/utils': 3.1.0 - '@mermaid-js/parser': 0.6.3 + '@braintree/sanitize-url': 7.1.2 + '@iconify/utils': 3.1.3 + '@mermaid-js/parser': 1.1.1 '@types/d3': 7.4.3 - cytoscape: 3.33.1 - cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.1) - cytoscape-fcose: 2.2.0(cytoscape@3.33.1) + '@upsetjs/venn.js': 2.0.0 + cytoscape: 3.33.4 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.4) + cytoscape-fcose: 2.2.0(cytoscape@3.33.4) d3: 7.9.0 d3-sankey: 0.12.3 - dagre-d3-es: 7.0.13 - dayjs: 1.11.19 - dompurify: 3.3.1 - katex: 0.16.27 + dagre-d3-es: 7.0.14 + dayjs: 1.11.20 + dompurify: 3.4.5 + es-toolkit: 1.46.1 + katex: 0.16.47 khroma: 2.1.0 - lodash-es: 4.18.1 marked: 16.4.2 roughjs: 4.6.6 - stylis: 4.3.6 + stylis: 4.4.0 ts-dedent: 2.2.0 - uuid: 11.1.0 + uuid: 14.0.0 methods@1.1.2: {} micromark-core-commonmark@2.0.3: dependencies: - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 devlop: 1.1.0 micromark-factory-destination: 2.0.1 micromark-factory-label: 2.0.1 @@ -22314,7 +22810,7 @@ snapshots: micromark-extension-mdx-expression@3.0.1: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 devlop: 1.1.0 micromark-factory-mdx-expression: 2.0.3 micromark-factory-space: 2.0.1 @@ -22325,7 +22821,7 @@ snapshots: micromark-extension-mdx-jsx@3.0.2: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 devlop: 1.1.0 estree-util-is-identifier-name: 3.0.0 micromark-factory-mdx-expression: 2.0.3 @@ -22342,7 +22838,7 @@ snapshots: micromark-extension-mdxjs-esm@3.0.0: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 devlop: 1.1.0 micromark-core-commonmark: 2.0.3 micromark-util-character: 2.1.1 @@ -22378,7 +22874,7 @@ snapshots: micromark-factory-mdx-expression@2.0.3: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 devlop: 1.1.0 micromark-factory-space: 2.0.1 micromark-util-character: 2.1.1 @@ -22443,7 +22939,7 @@ snapshots: micromark-util-decode-string@2.0.1: dependencies: - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 micromark-util-character: 2.1.1 micromark-util-decode-numeric-character-reference: 2.0.2 micromark-util-symbol: 2.0.1 @@ -22452,7 +22948,7 @@ snapshots: micromark-util-events-to-acorn@2.0.3: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/unist': 3.0.3 devlop: 1.1.0 estree-util-visit: 2.0.0 @@ -22493,9 +22989,9 @@ snapshots: micromark@4.0.2: dependencies: - '@types/debug': 4.1.12 + '@types/debug': 4.1.13 debug: 4.4.3 - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 devlop: 1.1.0 micromark-core-commonmark: 2.0.3 micromark-factory-space: 2.0.1 @@ -22550,21 +23046,21 @@ snapshots: min-indent@1.0.1: {} - mini-css-extract-plugin@2.9.4(webpack@5.106.2): + mini-css-extract-plugin@2.10.2(webpack@5.107.0(postcss@8.5.15)): dependencies: schema-utils: 4.3.3 tapable: 2.3.3 - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) minimalistic-assert@1.0.1: {} minimatch@10.2.5: dependencies: - brace-expansion: 5.0.5 + brace-expansion: 5.0.6 minimatch@3.1.5: dependencies: - brace-expansion: 1.1.12 + brace-expansion: 1.1.14 minimatch@5.1.9: dependencies: @@ -22576,30 +23072,6 @@ snapshots: minimist@1.2.8: {} - minipass-collect@2.0.1: - dependencies: - minipass: 7.1.3 - - minipass-fetch@5.0.1: - dependencies: - minipass: 7.1.3 - minipass-sized: 2.0.0 - minizlib: 3.1.0 - optionalDependencies: - encoding: 0.1.13 - - minipass-flush@1.0.5: - dependencies: - minipass: 3.3.6 - - minipass-pipeline@1.2.4: - dependencies: - minipass: 3.3.6 - - minipass-sized@2.0.0: - dependencies: - minipass: 7.1.3 - minipass@3.3.6: dependencies: yallist: 4.0.0 @@ -22625,14 +23097,7 @@ snapshots: mkdirp@3.0.1: {} - mlly@1.8.0: - dependencies: - acorn: 8.16.0 - pathe: 2.0.3 - pkg-types: 1.3.1 - ufo: 1.6.2 - - mnemonist@0.40.3: + mnemonist@0.40.4: dependencies: obliterator: 2.0.5 @@ -22662,7 +23127,7 @@ snapshots: '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3 optional: true - msgpackr@1.11.5: + msgpackr@2.0.1: optionalDependencies: msgpackr-extract: 3.0.3 @@ -22684,18 +23149,20 @@ snapshots: mute-stream@2.0.0: {} + mylas@2.1.14: {} + mz@2.7.0: dependencies: any-promise: 1.3.0 object-assign: 4.1.1 thenify-all: 1.6.0 - nan@2.26.2: + nan@2.27.0: optional: true - nanoid@3.3.11: {} + nanoid@3.3.12: {} - nanoid@5.1.9: {} + nanoid@5.1.11: {} napi-build-utils@2.0.0: optional: true @@ -22719,51 +23186,51 @@ snapshots: neo-async@2.6.2: {} - nest-commander@3.20.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@types/inquirer@8.2.12)(@types/node@24.12.2)(typescript@6.0.3): + nest-commander@3.20.1(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(@types/inquirer@8.2.12)(@types/node@24.12.4)(typescript@6.0.3): dependencies: '@fig/complete-commander': 3.2.0(commander@11.1.0) - '@golevelup/nestjs-discovery': 5.0.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@golevelup/nestjs-discovery': 5.0.0(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@types/inquirer': 8.2.12 commander: 11.1.0 cosmiconfig: 8.3.6(typescript@6.0.3) - inquirer: 8.2.7(@types/node@24.12.2) + inquirer: 8.2.7(@types/node@24.12.4) transitivePeerDependencies: - '@types/node' - typescript - nestjs-cls@6.2.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2): + nestjs-cls@6.2.0(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2): dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) reflect-metadata: 0.2.2 rxjs: 7.8.2 - nestjs-kysely@3.1.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(kysely@0.28.17)(reflect-metadata@0.2.2): + nestjs-kysely@3.1.2(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(kysely@0.28.17)(reflect-metadata@0.2.2): dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) kysely: 0.28.17 reflect-metadata: 0.2.2 tslib: 2.8.1 - nestjs-otel@8.0.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19): + nestjs-otel@8.0.3(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21): dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@opentelemetry/api': 1.9.1 '@opentelemetry/host-metrics': 0.38.3(@opentelemetry/api@1.9.1) tslib: 2.8.1 - nestjs-zod@5.3.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/swagger@11.4.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(class-transformer@0.5.1)(reflect-metadata@0.2.2))(rxjs@7.8.2)(zod@4.3.6): + nestjs-zod@5.4.0(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/swagger@11.4.3(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(reflect-metadata@0.2.2))(rxjs@7.8.2)(zod@4.3.6): dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) deepmerge: 4.3.1 rxjs: 7.8.2 zod: 4.3.6 optionalDependencies: - '@nestjs/swagger': 11.4.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(class-transformer@0.5.1)(reflect-metadata@0.2.2) + '@nestjs/swagger': 11.4.3(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(reflect-metadata@0.2.2) next-tick@1.1.0: {} @@ -22774,7 +23241,7 @@ snapshots: node-abi@3.92.0: dependencies: - semver: 7.7.4 + semver: 7.8.0 optional: true node-abort-controller@3.1.1: {} @@ -22784,7 +23251,7 @@ snapshots: node-addon-api@7.1.1: optional: true - node-addon-api@8.5.0: {} + node-addon-api@8.7.0: {} node-emoji@1.11.0: dependencies: @@ -22797,13 +23264,9 @@ snapshots: emojilib: 2.4.0 skin-tone: 2.0.0 - node-fetch@2.7.0(encoding@0.1.13): + node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 - optionalDependencies: - encoding: 0.1.13 - - node-forge@1.3.3: {} node-gyp-build-optional-packages@5.2.2: dependencies: @@ -22812,24 +23275,22 @@ snapshots: node-gyp-build@4.8.4: {} - node-gyp@12.2.0: + node-gyp@12.3.0: dependencies: env-paths: 2.2.1 exponential-backoff: 3.1.3 graceful-fs: 4.2.11 - make-fetch-happen: 15.0.3 nopt: 9.0.0 proc-log: 6.1.0 - semver: 7.7.4 - tar: 7.5.7 + semver: 7.8.0 + tar: 7.5.15 tinyglobby: 0.2.16 + undici: 6.25.0 which: 6.0.1 - transitivePeerDependencies: - - supports-color - node-releases@2.0.38: {} + node-releases@2.0.44: {} - nodemailer@8.0.5: {} + nodemailer@8.0.7: {} nopt@1.0.10: dependencies: @@ -22845,7 +23306,7 @@ snapshots: normalize-path@3.0.0: {} - normalize-url@8.1.0: {} + normalize-url@8.1.1: {} not@0.1.0: {} @@ -22868,11 +23329,11 @@ snapshots: dependencies: boolbase: 1.0.0 - null-loader@4.0.1(webpack@5.106.2): + null-loader@4.0.1(webpack@5.107.0(postcss@8.5.15)): dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) nwsapi@2.2.23: optional: true @@ -22881,9 +23342,9 @@ snapshots: dependencies: citty: 0.2.2 pathe: 2.0.3 - tinyexec: 1.1.1 + tinyexec: 1.1.2 - oauth4webapi@3.8.5: {} + oauth4webapi@3.8.6: {} object-assign@4.1.1: {} @@ -22895,7 +23356,7 @@ snapshots: object.assign@4.1.7: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 @@ -22908,16 +23369,16 @@ snapshots: obug@2.1.1: {} - oidc-provider@9.8.2: + oidc-provider@9.8.3: dependencies: '@koa/cors': 5.0.0 - '@koa/router': 15.4.0(koa@3.2.0) + '@koa/router': 15.5.0(koa@3.2.0) debug: 4.4.3 - eta: 4.5.1 - jose: 6.2.2 + eta: 4.6.0 + jose: 6.2.3 jsesc: 3.1.0 koa: 3.2.0 - nanoid: 5.1.9 + nanoid: 5.1.11 quick-lru: 7.3.0 raw-body: 3.0.2 transitivePeerDependencies: @@ -22943,14 +23404,14 @@ snapshots: open@10.2.0: dependencies: - default-browser: 5.4.0 + default-browser: 5.5.0 define-lazy-prop: 3.0.0 is-inside-container: 1.0.0 wsl-utils: 0.1.0 open@11.0.0: dependencies: - default-browser: 5.4.0 + default-browser: 5.5.0 define-lazy-prop: 3.0.0 is-in-ssh: 1.0.0 is-inside-container: 1.0.0 @@ -22965,10 +23426,10 @@ snapshots: opener@1.5.2: {} - openid-client@6.8.3: + openid-client@6.8.4: dependencies: - jose: 6.2.2 - oauth4webapi: 3.8.5 + jose: 6.2.3 + oauth4webapi: 3.8.6 optionator@0.9.4: dependencies: @@ -23035,8 +23496,6 @@ snapshots: dependencies: aggregate-error: 3.1.0 - p-map@7.0.4: {} - p-queue@6.6.2: dependencies: eventemitter3: 4.0.7 @@ -23045,7 +23504,7 @@ snapshots: p-retry@6.2.1: dependencies: '@types/retry': 0.12.2 - is-network-error: 1.3.0 + is-network-error: 1.3.2 retry: 0.13.1 p-timeout@3.2.0: @@ -23059,9 +23518,9 @@ snapshots: package-json@8.1.1: dependencies: got: 12.6.1 - registry-auth-token: 5.1.0 + registry-auth-token: 5.1.1 registry-url: 6.0.1 - semver: 7.7.4 + semver: 7.8.0 package-manager-detector@1.6.0: {} @@ -23079,7 +23538,7 @@ snapshots: '@types/unist': 2.0.11 character-entities-legacy: 3.0.0 character-reference-invalid: 2.0.1 - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 is-alphanumerical: 2.0.1 is-decimal: 2.0.1 is-hexadecimal: 2.0.1 @@ -23143,7 +23602,7 @@ snapshots: path-scurry@2.0.2: dependencies: - lru-cache: 11.3.5 + lru-cache: 11.5.0 minipass: 7.1.3 path-source@0.1.3: @@ -23178,18 +23637,18 @@ snapshots: peberminta@0.9.0: {} - pg-cloudflare@1.3.0: + pg-cloudflare@1.4.0: optional: true - pg-connection-string@2.12.0: {} + pg-connection-string@2.13.0: {} pg-int8@1.0.1: {} - pg-pool@3.13.0(pg@8.20.0): + pg-pool@3.14.0(pg@8.21.0): dependencies: - pg: 8.20.0 + pg: 8.21.0 - pg-protocol@1.13.0: {} + pg-protocol@1.14.0: {} pg-types@2.2.0: dependencies: @@ -23199,15 +23658,15 @@ snapshots: postgres-date: 1.0.7 postgres-interval: 1.2.0 - pg@8.20.0: + pg@8.21.0: dependencies: - pg-connection-string: 2.12.0 - pg-pool: 3.13.0(pg@8.20.0) - pg-protocol: 1.13.0 + pg-connection-string: 2.13.0 + pg-pool: 3.14.0(pg@8.21.0) + pg-protocol: 1.14.0 pg-types: 2.2.0 pgpass: 1.0.5 optionalDependencies: - pg-cloudflare: 1.3.0 + pg-cloudflare: 1.4.0 pgpass@1.0.5: dependencies: @@ -23227,30 +23686,37 @@ snapshots: dependencies: find-up: 6.3.0 - pkg-types@1.3.1: + pkijs@3.4.0: dependencies: - confbox: 0.1.8 - mlly: 1.8.0 - pathe: 2.0.3 + '@noble/hashes': 1.4.0 + asn1js: 3.0.10 + bytestreamjs: 2.0.1 + pvtsutils: 1.3.6 + pvutils: 1.1.5 + tslib: 2.8.1 - playwright-core@1.59.1: {} + playwright-core@1.60.0: {} - playwright@1.59.1: + playwright@1.60.0: dependencies: - playwright-core: 1.59.1 + playwright-core: 1.60.0 optionalDependencies: fsevents: 2.3.2 + plimit-lit@1.6.1: + dependencies: + queue-lit: 1.5.2 + pluralize@8.0.0: {} pmtiles@3.2.1: dependencies: '@types/leaflet': 1.9.21 - fflate: 0.8.2 + fflate: 0.8.3 pmtiles@4.4.1: dependencies: - fflate: 0.8.2 + fflate: 0.8.3 pngjs@5.0.0: {} @@ -23267,446 +23733,448 @@ snapshots: path-data-parser: 0.1.0 points-on-curve: 0.2.0 - postcss-attribute-case-insensitive@7.0.1(postcss@8.5.12): + postcss-attribute-case-insensitive@7.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 - postcss-calc@9.0.1(postcss@8.5.12): + postcss-calc@9.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 6.1.2 postcss-value-parser: 4.2.0 - postcss-clamp@4.1.0(postcss@8.5.12): + postcss-clamp@4.1.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-color-functional-notation@7.0.12(postcss@8.5.12): + postcss-color-functional-notation@7.0.12(postcss@8.5.15): dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - postcss-color-hex-alpha@10.0.0(postcss@8.5.12): + postcss-color-hex-alpha@10.0.0(postcss@8.5.15): dependencies: - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-color-rebeccapurple@10.0.0(postcss@8.5.12): + postcss-color-rebeccapurple@10.0.0(postcss@8.5.15): dependencies: - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-colormin@6.1.0(postcss@8.5.12): + postcss-colormin@6.1.0(postcss@8.5.15): dependencies: browserslist: 4.28.2 caniuse-api: 3.0.0 colord: 2.9.3 - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-convert-values@6.1.0(postcss@8.5.12): + postcss-convert-values@6.1.0(postcss@8.5.15): dependencies: browserslist: 4.28.2 - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-custom-media@11.0.6(postcss@8.5.12): + postcss-custom-media@11.0.6(postcss@8.5.15): dependencies: '@csstools/cascade-layer-name-parser': 2.0.5(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 '@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - postcss: 8.5.12 + postcss: 8.5.15 - postcss-custom-properties@14.0.6(postcss@8.5.12): + postcss-custom-properties@14.0.6(postcss@8.5.15): dependencies: '@csstools/cascade-layer-name-parser': 2.0.5(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-custom-selectors@8.0.5(postcss@8.5.12): + postcss-custom-selectors@8.0.5(postcss@8.5.15): dependencies: '@csstools/cascade-layer-name-parser': 2.0.5(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 - postcss-dir-pseudo-class@9.0.1(postcss@8.5.12): + postcss-dir-pseudo-class@9.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 - postcss-discard-comments@6.0.2(postcss@8.5.12): + postcss-discard-comments@6.0.2(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss-discard-duplicates@6.0.3(postcss@8.5.12): + postcss-discard-duplicates@6.0.3(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss-discard-empty@6.0.3(postcss@8.5.12): + postcss-discard-empty@6.0.3(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss-discard-overridden@6.0.2(postcss@8.5.12): + postcss-discard-overridden@6.0.2(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss-discard-unused@6.0.5(postcss@8.5.12): + postcss-discard-unused@6.0.5(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 6.1.2 - postcss-double-position-gradients@6.0.4(postcss@8.5.12): + postcss-double-position-gradients@6.0.4(postcss@8.5.15): dependencies: - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-focus-visible@10.0.1(postcss@8.5.12): + postcss-focus-visible@10.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 - postcss-focus-within@9.0.1(postcss@8.5.12): + postcss-focus-within@9.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 - postcss-font-variant@5.0.0(postcss@8.5.12): + postcss-font-variant@5.0.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss-gap-properties@6.0.0(postcss@8.5.12): + postcss-gap-properties@6.0.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss-image-set-function@7.0.0(postcss@8.5.12): + postcss-image-set-function@7.0.0(postcss@8.5.15): dependencies: - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-import@15.1.0(postcss@8.5.12): + postcss-import@15.1.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 read-cache: 1.0.0 - resolve: 1.22.11 + resolve: 1.22.12 - postcss-js@4.1.0(postcss@8.5.12): + postcss-js@4.1.0(postcss@8.5.15): dependencies: camelcase-css: 2.0.1 - postcss: 8.5.12 + postcss: 8.5.15 - postcss-lab-function@7.0.12(postcss@8.5.12): + postcss-lab-function@7.0.12(postcss@8.5.15): dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - postcss-load-config@3.1.4(postcss@8.5.12): + postcss-load-config@3.1.4(postcss@8.5.15): dependencies: lilconfig: 2.1.0 yaml: 1.10.3 optionalDependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.12)(tsx@4.21.0)(yaml@2.8.3): + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.15)(tsx@4.22.3)(yaml@2.9.0): dependencies: lilconfig: 3.1.3 optionalDependencies: jiti: 1.21.7 - postcss: 8.5.12 - tsx: 4.21.0 - yaml: 2.8.3 + postcss: 8.5.15 + tsx: 4.22.3 + yaml: 2.9.0 - postcss-loader@7.3.4(postcss@8.5.12)(typescript@6.0.3)(webpack@5.106.2): + postcss-loader@7.3.4(postcss@8.5.15)(typescript@6.0.3)(webpack@5.107.0(postcss@8.5.15)): dependencies: cosmiconfig: 8.3.6(typescript@6.0.3) jiti: 1.21.7 - postcss: 8.5.12 - semver: 7.7.4 - webpack: 5.106.2 + postcss: 8.5.15 + semver: 7.8.0 + webpack: 5.107.0(postcss@8.5.15) transitivePeerDependencies: - typescript - postcss-logical@8.1.0(postcss@8.5.12): + postcss-logical@8.1.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-merge-idents@6.0.3(postcss@8.5.12): + postcss-merge-idents@6.0.3(postcss@8.5.15): dependencies: - cssnano-utils: 4.0.2(postcss@8.5.12) - postcss: 8.5.12 + cssnano-utils: 4.0.2(postcss@8.5.15) + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-merge-longhand@6.0.5(postcss@8.5.12): + postcss-merge-longhand@6.0.5(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - stylehacks: 6.1.1(postcss@8.5.12) + stylehacks: 6.1.1(postcss@8.5.15) - postcss-merge-rules@6.1.1(postcss@8.5.12): + postcss-merge-rules@6.1.1(postcss@8.5.15): dependencies: browserslist: 4.28.2 caniuse-api: 3.0.0 - cssnano-utils: 4.0.2(postcss@8.5.12) - postcss: 8.5.12 + cssnano-utils: 4.0.2(postcss@8.5.15) + postcss: 8.5.15 postcss-selector-parser: 6.1.2 - postcss-minify-font-values@6.1.0(postcss@8.5.12): + postcss-minify-font-values@6.1.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-minify-gradients@6.0.3(postcss@8.5.12): + postcss-minify-gradients@6.0.3(postcss@8.5.15): dependencies: colord: 2.9.3 - cssnano-utils: 4.0.2(postcss@8.5.12) - postcss: 8.5.12 + cssnano-utils: 4.0.2(postcss@8.5.15) + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-minify-params@6.1.0(postcss@8.5.12): + postcss-minify-params@6.1.0(postcss@8.5.15): dependencies: browserslist: 4.28.2 - cssnano-utils: 4.0.2(postcss@8.5.12) - postcss: 8.5.12 + cssnano-utils: 4.0.2(postcss@8.5.15) + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-minify-selectors@6.0.4(postcss@8.5.12): + postcss-minify-selectors@6.0.4(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 6.1.2 - postcss-modules-extract-imports@3.1.0(postcss@8.5.12): + postcss-modules-extract-imports@3.1.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss-modules-local-by-default@4.2.0(postcss@8.5.12): + postcss-modules-local-by-default@4.2.0(postcss@8.5.15): dependencies: - icss-utils: 5.1.0(postcss@8.5.12) - postcss: 8.5.12 + icss-utils: 5.1.0(postcss@8.5.15) + postcss: 8.5.15 postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 - postcss-modules-scope@3.2.1(postcss@8.5.12): + postcss-modules-scope@3.2.1(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 - postcss-modules-values@4.0.0(postcss@8.5.12): + postcss-modules-values@4.0.0(postcss@8.5.15): dependencies: - icss-utils: 5.1.0(postcss@8.5.12) - postcss: 8.5.12 + icss-utils: 5.1.0(postcss@8.5.15) + postcss: 8.5.15 - postcss-nested@6.2.0(postcss@8.5.12): + postcss-nested@6.2.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 6.1.2 - postcss-nesting@13.0.2(postcss@8.5.12): + postcss-nesting@13.0.2(postcss@8.5.15): dependencies: '@csstools/selector-resolve-nested': 3.1.0(postcss-selector-parser@7.1.1) '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.1) - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 - postcss-normalize-charset@6.0.2(postcss@8.5.12): + postcss-normalize-charset@6.0.2(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss-normalize-display-values@6.0.2(postcss@8.5.12): + postcss-normalize-display-values@6.0.2(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-normalize-positions@6.0.2(postcss@8.5.12): + postcss-normalize-positions@6.0.2(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-normalize-repeat-style@6.0.2(postcss@8.5.12): + postcss-normalize-repeat-style@6.0.2(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-normalize-string@6.0.2(postcss@8.5.12): + postcss-normalize-string@6.0.2(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-normalize-timing-functions@6.0.2(postcss@8.5.12): + postcss-normalize-timing-functions@6.0.2(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-normalize-unicode@6.1.0(postcss@8.5.12): + postcss-normalize-unicode@6.1.0(postcss@8.5.15): dependencies: browserslist: 4.28.2 - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-normalize-url@6.0.2(postcss@8.5.12): + postcss-normalize-url@6.0.2(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-normalize-whitespace@6.0.2(postcss@8.5.12): + postcss-normalize-whitespace@6.0.2(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-opacity-percentage@3.0.0(postcss@8.5.12): + postcss-opacity-percentage@3.0.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss-ordered-values@6.0.2(postcss@8.5.12): + postcss-ordered-values@6.0.2(postcss@8.5.15): dependencies: - cssnano-utils: 4.0.2(postcss@8.5.12) - postcss: 8.5.12 + cssnano-utils: 4.0.2(postcss@8.5.15) + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-overflow-shorthand@6.0.0(postcss@8.5.12): + postcss-overflow-shorthand@6.0.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-page-break@3.0.4(postcss@8.5.12): + postcss-page-break@3.0.4(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss-place@10.0.0(postcss@8.5.12): + postcss-place@10.0.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-preset-env@10.5.0(postcss@8.5.12): + postcss-preset-env@10.6.1(postcss@8.5.15): dependencies: - '@csstools/postcss-alpha-function': 1.0.1(postcss@8.5.12) - '@csstools/postcss-cascade-layers': 5.0.2(postcss@8.5.12) - '@csstools/postcss-color-function': 4.0.12(postcss@8.5.12) - '@csstools/postcss-color-function-display-p3-linear': 1.0.1(postcss@8.5.12) - '@csstools/postcss-color-mix-function': 3.0.12(postcss@8.5.12) - '@csstools/postcss-color-mix-variadic-function-arguments': 1.0.2(postcss@8.5.12) - '@csstools/postcss-content-alt-text': 2.0.8(postcss@8.5.12) - '@csstools/postcss-contrast-color-function': 2.0.12(postcss@8.5.12) - '@csstools/postcss-exponential-functions': 2.0.9(postcss@8.5.12) - '@csstools/postcss-font-format-keywords': 4.0.0(postcss@8.5.12) - '@csstools/postcss-gamut-mapping': 2.0.11(postcss@8.5.12) - '@csstools/postcss-gradients-interpolation-method': 5.0.12(postcss@8.5.12) - '@csstools/postcss-hwb-function': 4.0.12(postcss@8.5.12) - '@csstools/postcss-ic-unit': 4.0.4(postcss@8.5.12) - '@csstools/postcss-initial': 2.0.1(postcss@8.5.12) - '@csstools/postcss-is-pseudo-class': 5.0.3(postcss@8.5.12) - '@csstools/postcss-light-dark-function': 2.0.11(postcss@8.5.12) - '@csstools/postcss-logical-float-and-clear': 3.0.0(postcss@8.5.12) - '@csstools/postcss-logical-overflow': 2.0.0(postcss@8.5.12) - '@csstools/postcss-logical-overscroll-behavior': 2.0.0(postcss@8.5.12) - '@csstools/postcss-logical-resize': 3.0.0(postcss@8.5.12) - '@csstools/postcss-logical-viewport-units': 3.0.4(postcss@8.5.12) - '@csstools/postcss-media-minmax': 2.0.9(postcss@8.5.12) - '@csstools/postcss-media-queries-aspect-ratio-number-values': 3.0.5(postcss@8.5.12) - '@csstools/postcss-nested-calc': 4.0.0(postcss@8.5.12) - '@csstools/postcss-normalize-display-values': 4.0.0(postcss@8.5.12) - '@csstools/postcss-oklab-function': 4.0.12(postcss@8.5.12) - '@csstools/postcss-position-area-property': 1.0.0(postcss@8.5.12) - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/postcss-random-function': 2.0.1(postcss@8.5.12) - '@csstools/postcss-relative-color-syntax': 3.0.12(postcss@8.5.12) - '@csstools/postcss-scope-pseudo-class': 4.0.1(postcss@8.5.12) - '@csstools/postcss-sign-functions': 1.1.4(postcss@8.5.12) - '@csstools/postcss-stepped-value-functions': 4.0.9(postcss@8.5.12) - '@csstools/postcss-system-ui-font-family': 1.0.0(postcss@8.5.12) - '@csstools/postcss-text-decoration-shorthand': 4.0.3(postcss@8.5.12) - '@csstools/postcss-trigonometric-functions': 4.0.9(postcss@8.5.12) - '@csstools/postcss-unset-value': 4.0.0(postcss@8.5.12) - autoprefixer: 10.5.0(postcss@8.5.12) + '@csstools/postcss-alpha-function': 1.0.1(postcss@8.5.15) + '@csstools/postcss-cascade-layers': 5.0.2(postcss@8.5.15) + '@csstools/postcss-color-function': 4.0.12(postcss@8.5.15) + '@csstools/postcss-color-function-display-p3-linear': 1.0.1(postcss@8.5.15) + '@csstools/postcss-color-mix-function': 3.0.12(postcss@8.5.15) + '@csstools/postcss-color-mix-variadic-function-arguments': 1.0.2(postcss@8.5.15) + '@csstools/postcss-content-alt-text': 2.0.8(postcss@8.5.15) + '@csstools/postcss-contrast-color-function': 2.0.12(postcss@8.5.15) + '@csstools/postcss-exponential-functions': 2.0.9(postcss@8.5.15) + '@csstools/postcss-font-format-keywords': 4.0.0(postcss@8.5.15) + '@csstools/postcss-gamut-mapping': 2.0.11(postcss@8.5.15) + '@csstools/postcss-gradients-interpolation-method': 5.0.12(postcss@8.5.15) + '@csstools/postcss-hwb-function': 4.0.12(postcss@8.5.15) + '@csstools/postcss-ic-unit': 4.0.4(postcss@8.5.15) + '@csstools/postcss-initial': 2.0.1(postcss@8.5.15) + '@csstools/postcss-is-pseudo-class': 5.0.3(postcss@8.5.15) + '@csstools/postcss-light-dark-function': 2.0.11(postcss@8.5.15) + '@csstools/postcss-logical-float-and-clear': 3.0.0(postcss@8.5.15) + '@csstools/postcss-logical-overflow': 2.0.0(postcss@8.5.15) + '@csstools/postcss-logical-overscroll-behavior': 2.0.0(postcss@8.5.15) + '@csstools/postcss-logical-resize': 3.0.0(postcss@8.5.15) + '@csstools/postcss-logical-viewport-units': 3.0.4(postcss@8.5.15) + '@csstools/postcss-media-minmax': 2.0.9(postcss@8.5.15) + '@csstools/postcss-media-queries-aspect-ratio-number-values': 3.0.5(postcss@8.5.15) + '@csstools/postcss-nested-calc': 4.0.0(postcss@8.5.15) + '@csstools/postcss-normalize-display-values': 4.0.1(postcss@8.5.15) + '@csstools/postcss-oklab-function': 4.0.12(postcss@8.5.15) + '@csstools/postcss-position-area-property': 1.0.0(postcss@8.5.15) + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/postcss-property-rule-prelude-list': 1.0.0(postcss@8.5.15) + '@csstools/postcss-random-function': 2.0.1(postcss@8.5.15) + '@csstools/postcss-relative-color-syntax': 3.0.12(postcss@8.5.15) + '@csstools/postcss-scope-pseudo-class': 4.0.1(postcss@8.5.15) + '@csstools/postcss-sign-functions': 1.1.4(postcss@8.5.15) + '@csstools/postcss-stepped-value-functions': 4.0.9(postcss@8.5.15) + '@csstools/postcss-syntax-descriptor-syntax-production': 1.0.1(postcss@8.5.15) + '@csstools/postcss-system-ui-font-family': 1.0.0(postcss@8.5.15) + '@csstools/postcss-text-decoration-shorthand': 4.0.3(postcss@8.5.15) + '@csstools/postcss-trigonometric-functions': 4.0.9(postcss@8.5.15) + '@csstools/postcss-unset-value': 4.0.0(postcss@8.5.15) + autoprefixer: 10.5.0(postcss@8.5.15) browserslist: 4.28.2 - css-blank-pseudo: 7.0.1(postcss@8.5.12) - css-has-pseudo: 7.0.3(postcss@8.5.12) - css-prefers-color-scheme: 10.0.0(postcss@8.5.12) - cssdb: 8.5.2 - postcss: 8.5.12 - postcss-attribute-case-insensitive: 7.0.1(postcss@8.5.12) - postcss-clamp: 4.1.0(postcss@8.5.12) - postcss-color-functional-notation: 7.0.12(postcss@8.5.12) - postcss-color-hex-alpha: 10.0.0(postcss@8.5.12) - postcss-color-rebeccapurple: 10.0.0(postcss@8.5.12) - postcss-custom-media: 11.0.6(postcss@8.5.12) - postcss-custom-properties: 14.0.6(postcss@8.5.12) - postcss-custom-selectors: 8.0.5(postcss@8.5.12) - postcss-dir-pseudo-class: 9.0.1(postcss@8.5.12) - postcss-double-position-gradients: 6.0.4(postcss@8.5.12) - postcss-focus-visible: 10.0.1(postcss@8.5.12) - postcss-focus-within: 9.0.1(postcss@8.5.12) - postcss-font-variant: 5.0.0(postcss@8.5.12) - postcss-gap-properties: 6.0.0(postcss@8.5.12) - postcss-image-set-function: 7.0.0(postcss@8.5.12) - postcss-lab-function: 7.0.12(postcss@8.5.12) - postcss-logical: 8.1.0(postcss@8.5.12) - postcss-nesting: 13.0.2(postcss@8.5.12) - postcss-opacity-percentage: 3.0.0(postcss@8.5.12) - postcss-overflow-shorthand: 6.0.0(postcss@8.5.12) - postcss-page-break: 3.0.4(postcss@8.5.12) - postcss-place: 10.0.0(postcss@8.5.12) - postcss-pseudo-class-any-link: 10.0.1(postcss@8.5.12) - postcss-replace-overflow-wrap: 4.0.0(postcss@8.5.12) - postcss-selector-not: 8.0.1(postcss@8.5.12) + css-blank-pseudo: 7.0.1(postcss@8.5.15) + css-has-pseudo: 7.0.3(postcss@8.5.15) + css-prefers-color-scheme: 10.0.0(postcss@8.5.15) + cssdb: 8.9.0 + postcss: 8.5.15 + postcss-attribute-case-insensitive: 7.0.1(postcss@8.5.15) + postcss-clamp: 4.1.0(postcss@8.5.15) + postcss-color-functional-notation: 7.0.12(postcss@8.5.15) + postcss-color-hex-alpha: 10.0.0(postcss@8.5.15) + postcss-color-rebeccapurple: 10.0.0(postcss@8.5.15) + postcss-custom-media: 11.0.6(postcss@8.5.15) + postcss-custom-properties: 14.0.6(postcss@8.5.15) + postcss-custom-selectors: 8.0.5(postcss@8.5.15) + postcss-dir-pseudo-class: 9.0.1(postcss@8.5.15) + postcss-double-position-gradients: 6.0.4(postcss@8.5.15) + postcss-focus-visible: 10.0.1(postcss@8.5.15) + postcss-focus-within: 9.0.1(postcss@8.5.15) + postcss-font-variant: 5.0.0(postcss@8.5.15) + postcss-gap-properties: 6.0.0(postcss@8.5.15) + postcss-image-set-function: 7.0.0(postcss@8.5.15) + postcss-lab-function: 7.0.12(postcss@8.5.15) + postcss-logical: 8.1.0(postcss@8.5.15) + postcss-nesting: 13.0.2(postcss@8.5.15) + postcss-opacity-percentage: 3.0.0(postcss@8.5.15) + postcss-overflow-shorthand: 6.0.0(postcss@8.5.15) + postcss-page-break: 3.0.4(postcss@8.5.15) + postcss-place: 10.0.0(postcss@8.5.15) + postcss-pseudo-class-any-link: 10.0.1(postcss@8.5.15) + postcss-replace-overflow-wrap: 4.0.0(postcss@8.5.15) + postcss-selector-not: 8.0.1(postcss@8.5.15) - postcss-pseudo-class-any-link@10.0.1(postcss@8.5.12): + postcss-pseudo-class-any-link@10.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 - postcss-reduce-idents@6.0.3(postcss@8.5.12): + postcss-reduce-idents@6.0.3(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-reduce-initial@6.1.0(postcss@8.5.12): + postcss-reduce-initial@6.1.0(postcss@8.5.15): dependencies: browserslist: 4.28.2 caniuse-api: 3.0.0 - postcss: 8.5.12 + postcss: 8.5.15 - postcss-reduce-transforms@6.0.2(postcss@8.5.12): + postcss-reduce-transforms@6.0.2(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-replace-overflow-wrap@4.0.0(postcss@8.5.12): + postcss-replace-overflow-wrap@4.0.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss-safe-parser@7.0.1(postcss@8.5.12): + postcss-safe-parser@7.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss-scss@4.0.9(postcss@8.5.12): + postcss-scss@4.0.9(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss-selector-not@8.0.1(postcss@8.5.12): + postcss-selector-not@8.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 postcss-selector-parser@6.1.2: @@ -23719,31 +24187,31 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-sort-media-queries@5.2.0(postcss@8.5.12): + postcss-sort-media-queries@5.2.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 sort-css-media-queries: 2.2.0 - postcss-svgo@6.0.3(postcss@8.5.12): + postcss-svgo@6.0.3(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - svgo: 3.3.2 + svgo: 3.3.3 - postcss-unique-selectors@6.0.4(postcss@8.5.12): + postcss-unique-selectors@6.0.4(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 6.1.2 postcss-value-parser@4.2.0: {} - postcss-zindex@6.0.2(postcss@8.5.12): + postcss-zindex@6.0.2(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss@8.5.12: + postcss@8.5.15: dependencies: - nanoid: 3.3.11 + nanoid: 3.3.12 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -23794,10 +24262,10 @@ snapshots: dependencies: prettier: 3.8.3 - prettier-plugin-svelte@3.5.1(prettier@3.8.3)(svelte@5.55.2): + prettier-plugin-svelte@3.5.2(prettier@3.8.3)(svelte@5.55.8(@typescript-eslint/types@8.59.4)): dependencies: prettier: 3.8.3 - svelte: 5.55.2 + svelte: 5.55.8(@typescript-eslint/types@8.59.4) prettier@3.8.3: {} @@ -23814,11 +24282,11 @@ snapshots: pretty-time@1.1.0: {} - prism-react-renderer@2.4.1(react@19.2.5): + prism-react-renderer@2.4.1(react@19.2.6): dependencies: - '@types/prismjs': 1.26.5 + '@types/prismjs': 1.26.6 clsx: 2.1.1 - react: 19.2.5 + react: 19.2.6 prismjs@1.30.0: {} @@ -23828,11 +24296,6 @@ snapshots: process@0.11.10: {} - promise-retry@2.0.1: - dependencies: - err-code: 2.0.3 - retry: 0.12.0 - prompts@2.4.2: dependencies: kleur: 3.0.3 @@ -23865,34 +24328,19 @@ snapshots: proto-list@1.2.4: {} - protobufjs@7.5.5: + protobufjs@7.6.0: dependencies: '@protobufjs/aspromise': 1.1.2 '@protobufjs/base64': 1.1.2 - '@protobufjs/codegen': 2.0.4 + '@protobufjs/codegen': 2.0.5 '@protobufjs/eventemitter': 1.1.0 - '@protobufjs/fetch': 1.1.0 + '@protobufjs/fetch': 1.1.1 '@protobufjs/float': 1.0.2 - '@protobufjs/inquire': 1.1.0 + '@protobufjs/inquire': 1.1.2 '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 - '@protobufjs/utf8': 1.1.0 - '@types/node': 24.12.2 - long: 5.3.2 - - protobufjs@8.0.1: - dependencies: - '@protobufjs/aspromise': 1.1.2 - '@protobufjs/base64': 1.1.2 - '@protobufjs/codegen': 2.0.4 - '@protobufjs/eventemitter': 1.1.0 - '@protobufjs/fetch': 1.1.0 - '@protobufjs/float': 1.0.2 - '@protobufjs/inquire': 1.1.0 - '@protobufjs/path': 1.1.2 - '@protobufjs/pool': 1.1.0 - '@protobufjs/utf8': 1.1.0 - '@types/node': 24.12.2 + '@protobufjs/utf8': 1.1.1 + '@types/node': 24.12.4 long: 5.3.2 protocol-buffers-schema@3.6.1: {} @@ -23915,16 +24363,24 @@ snapshots: dependencies: escape-goat: 4.0.0 + pvtsutils@1.3.6: + dependencies: + tslib: 2.8.1 + + pvutils@1.1.5: {} + qrcode@1.5.4: dependencies: dijkstrajs: 1.0.3 pngjs: 5.0.0 yargs: 15.4.1 - qs@6.14.1: + qs@6.15.2: dependencies: side-channel: 1.1.0 + queue-lit@1.5.2: {} + queue-microtask@1.2.3: {} quick-lru@5.1.1: {} @@ -23962,11 +24418,11 @@ snapshots: iconv-lite: 0.7.2 unpipe: 1.0.0 - raw-loader@4.0.2(webpack@5.106.2): + raw-loader@4.0.2(webpack@5.107.0(postcss@8.5.15)): dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) rc@1.2.8: dependencies: @@ -23975,9 +24431,9 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 - react-dom@19.2.5(react@19.2.5): + react-dom@19.2.6(react@19.2.6): dependencies: - react: 19.2.5 + react: 19.2.6 scheduler: 0.27.0 react-email@5.2.11: @@ -24010,34 +24466,34 @@ snapshots: react-is@17.0.2: {} - react-json-view-lite@2.5.0(react@19.2.5): + react-json-view-lite@2.5.0(react@19.2.6): dependencies: - react: 19.2.5 + react: 19.2.6 - react-loadable-ssr-addon-v5-slorber@1.0.3(@docusaurus/react-loadable@6.0.0(react@19.2.5))(webpack@5.106.2): + react-loadable-ssr-addon-v5-slorber@1.0.3(@docusaurus/react-loadable@6.0.0(react@19.2.6))(webpack@5.107.0(postcss@8.5.15)): dependencies: '@babel/runtime': 7.29.2 - react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.2.5)' - webpack: 5.106.2 + react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.2.6)' + webpack: 5.107.0(postcss@8.5.15) - react-router-config@5.1.1(react-router@5.3.4(react@19.2.5))(react@19.2.5): + react-router-config@5.1.1(react-router@5.3.4(react@19.2.6))(react@19.2.6): dependencies: '@babel/runtime': 7.29.2 - react: 19.2.5 - react-router: 5.3.4(react@19.2.5) + react: 19.2.6 + react-router: 5.3.4(react@19.2.6) - react-router-dom@5.3.4(react@19.2.5): + react-router-dom@5.3.4(react@19.2.6): dependencies: '@babel/runtime': 7.29.2 history: 4.10.1 loose-envify: 1.4.0 prop-types: 15.8.1 - react: 19.2.5 - react-router: 5.3.4(react@19.2.5) + react: 19.2.6 + react-router: 5.3.4(react@19.2.6) tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - react-router@5.3.4(react@19.2.5): + react-router@5.3.4(react@19.2.6): dependencies: '@babel/runtime': 7.29.2 history: 4.10.1 @@ -24045,12 +24501,12 @@ snapshots: loose-envify: 1.4.0 path-to-regexp: 1.9.0 prop-types: 15.8.1 - react: 19.2.5 + react: 19.2.6 react-is: 16.13.1 tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - react@19.2.5: {} + react@19.2.6: {} read-cache@1.0.0: dependencies: @@ -24092,7 +24548,7 @@ snapshots: recma-build-jsx@1.0.0: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 estree-util-build-jsx: 3.0.1 vfile: 6.0.3 @@ -24107,14 +24563,14 @@ snapshots: recma-parse@1.0.0: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 esast-util-from-js: 2.0.1 unified: 11.0.5 vfile: 6.0.3 recma-stringify@1.0.0: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 estree-util-to-js: 2.0.0 unified: 11.0.5 vfile: 6.0.3 @@ -24145,13 +24601,13 @@ snapshots: regenerate: 1.4.2 regenerate-unicode-properties: 10.2.2 regjsgen: 0.8.0 - regjsparser: 0.13.0 + regjsparser: 0.13.1 unicode-match-property-ecmascript: 2.0.0 unicode-match-property-value-ecmascript: 2.2.1 - registry-auth-token@5.1.0: + registry-auth-token@5.1.1: dependencies: - '@pnpm/npm-conf': 2.3.1 + '@pnpm/npm-conf': 3.0.2 registry-url@6.0.1: dependencies: @@ -24159,7 +24615,7 @@ snapshots: regjsgen@0.8.0: {} - regjsparser@0.13.0: + regjsparser@0.13.1: dependencies: jsesc: 3.1.0 @@ -24176,7 +24632,7 @@ snapshots: rehype-recma@1.0.0: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/hast': 3.0.4 hast-util-to-estree: 3.1.3 transitivePeerDependencies: @@ -24231,7 +24687,7 @@ snapshots: remark-parse@11.0.0: dependencies: '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 micromark-util-types: 2.0.2 unified: 11.0.5 transitivePeerDependencies: @@ -24290,9 +24746,10 @@ snapshots: dependencies: protocol-buffers-schema: 3.6.1 - resolve@1.22.11: + resolve@1.22.12: dependencies: - is-core-module: 2.16.1 + es-errors: 1.3.0 + is-core-module: 2.16.2 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -24324,66 +24781,66 @@ snapshots: robust-predicates@3.0.3: {} - rolldown@1.0.0-rc.17: + rolldown@1.0.1: dependencies: - '@oxc-project/types': 0.127.0 - '@rolldown/pluginutils': 1.0.0-rc.17 + '@oxc-project/types': 0.130.0 + '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-rc.17 - '@rolldown/binding-darwin-arm64': 1.0.0-rc.17 - '@rolldown/binding-darwin-x64': 1.0.0-rc.17 - '@rolldown/binding-freebsd-x64': 1.0.0-rc.17 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.17 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.17 - '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.17 - '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.17 - '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.17 - '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.17 - '@rolldown/binding-linux-x64-musl': 1.0.0-rc.17 - '@rolldown/binding-openharmony-arm64': 1.0.0-rc.17 - '@rolldown/binding-wasm32-wasi': 1.0.0-rc.17 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.17 - '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.17 + '@rolldown/binding-android-arm64': 1.0.1 + '@rolldown/binding-darwin-arm64': 1.0.1 + '@rolldown/binding-darwin-x64': 1.0.1 + '@rolldown/binding-freebsd-x64': 1.0.1 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.1 + '@rolldown/binding-linux-arm64-gnu': 1.0.1 + '@rolldown/binding-linux-arm64-musl': 1.0.1 + '@rolldown/binding-linux-ppc64-gnu': 1.0.1 + '@rolldown/binding-linux-s390x-gnu': 1.0.1 + '@rolldown/binding-linux-x64-gnu': 1.0.1 + '@rolldown/binding-linux-x64-musl': 1.0.1 + '@rolldown/binding-openharmony-arm64': 1.0.1 + '@rolldown/binding-wasm32-wasi': 1.0.1 + '@rolldown/binding-win32-arm64-msvc': 1.0.1 + '@rolldown/binding-win32-x64-msvc': 1.0.1 - rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-rc.17)(rollup@4.55.1): + rollup-plugin-visualizer@7.0.1(rolldown@1.0.1)(rollup@4.60.4): dependencies: open: 11.0.0 picomatch: 4.0.4 source-map: 0.7.6 yargs: 18.0.0 optionalDependencies: - rolldown: 1.0.0-rc.17 - rollup: 4.55.1 + rolldown: 1.0.1 + rollup: 4.60.4 - rollup@4.55.1: + rollup@4.60.4: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.55.1 - '@rollup/rollup-android-arm64': 4.55.1 - '@rollup/rollup-darwin-arm64': 4.55.1 - '@rollup/rollup-darwin-x64': 4.55.1 - '@rollup/rollup-freebsd-arm64': 4.55.1 - '@rollup/rollup-freebsd-x64': 4.55.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.55.1 - '@rollup/rollup-linux-arm-musleabihf': 4.55.1 - '@rollup/rollup-linux-arm64-gnu': 4.55.1 - '@rollup/rollup-linux-arm64-musl': 4.55.1 - '@rollup/rollup-linux-loong64-gnu': 4.55.1 - '@rollup/rollup-linux-loong64-musl': 4.55.1 - '@rollup/rollup-linux-ppc64-gnu': 4.55.1 - '@rollup/rollup-linux-ppc64-musl': 4.55.1 - '@rollup/rollup-linux-riscv64-gnu': 4.55.1 - '@rollup/rollup-linux-riscv64-musl': 4.55.1 - '@rollup/rollup-linux-s390x-gnu': 4.55.1 - '@rollup/rollup-linux-x64-gnu': 4.55.1 - '@rollup/rollup-linux-x64-musl': 4.55.1 - '@rollup/rollup-openbsd-x64': 4.55.1 - '@rollup/rollup-openharmony-arm64': 4.55.1 - '@rollup/rollup-win32-arm64-msvc': 4.55.1 - '@rollup/rollup-win32-ia32-msvc': 4.55.1 - '@rollup/rollup-win32-x64-gnu': 4.55.1 - '@rollup/rollup-win32-x64-msvc': 4.55.1 + '@rollup/rollup-android-arm-eabi': 4.60.4 + '@rollup/rollup-android-arm64': 4.60.4 + '@rollup/rollup-darwin-arm64': 4.60.4 + '@rollup/rollup-darwin-x64': 4.60.4 + '@rollup/rollup-freebsd-arm64': 4.60.4 + '@rollup/rollup-freebsd-x64': 4.60.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.4 + '@rollup/rollup-linux-arm-musleabihf': 4.60.4 + '@rollup/rollup-linux-arm64-gnu': 4.60.4 + '@rollup/rollup-linux-arm64-musl': 4.60.4 + '@rollup/rollup-linux-loong64-gnu': 4.60.4 + '@rollup/rollup-linux-loong64-musl': 4.60.4 + '@rollup/rollup-linux-ppc64-gnu': 4.60.4 + '@rollup/rollup-linux-ppc64-musl': 4.60.4 + '@rollup/rollup-linux-riscv64-gnu': 4.60.4 + '@rollup/rollup-linux-riscv64-musl': 4.60.4 + '@rollup/rollup-linux-s390x-gnu': 4.60.4 + '@rollup/rollup-linux-x64-gnu': 4.60.4 + '@rollup/rollup-linux-x64-musl': 4.60.4 + '@rollup/rollup-openbsd-x64': 4.60.4 + '@rollup/rollup-openharmony-arm64': 4.60.4 + '@rollup/rollup-win32-arm64-msvc': 4.60.4 + '@rollup/rollup-win32-ia32-msvc': 4.60.4 + '@rollup/rollup-win32-x64-gnu': 4.60.4 + '@rollup/rollup-win32-x64-msvc': 4.60.4 fsevents: 2.3.3 roughjs@4.6.6: @@ -24410,7 +24867,7 @@ snapshots: dependencies: escalade: 3.2.0 picocolors: 1.1.1 - postcss: 8.5.12 + postcss: 8.5.15 strip-json-comments: 3.1.1 run-applescript@7.1.0: {} @@ -24421,14 +24878,14 @@ snapshots: dependencies: queue-microtask: 1.2.3 - runed@0.35.1(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2): + runed@0.35.1(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4)): dependencies: dequal: 2.0.3 esm-env: 1.2.2 lz-string: 1.5.0 - svelte: 5.55.2 + svelte: 5.55.8(@typescript-eslint/types@8.59.4) optionalDependencies: - '@sveltejs/kit': 2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@sveltejs/kit': 2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) rw@1.3.3: {} @@ -24462,7 +24919,7 @@ snapshots: optionalDependencies: '@parcel/watcher': 2.5.6 - sax@1.4.3: {} + sax@1.6.0: {} saxes@6.0.0: dependencies: @@ -24476,15 +24933,15 @@ snapshots: schema-utils@3.3.0: dependencies: '@types/json-schema': 7.0.15 - ajv: 6.14.0 - ajv-keywords: 3.5.2(ajv@6.14.0) + ajv: 6.15.0 + ajv-keywords: 3.5.2(ajv@6.15.0) schema-utils@4.3.3: dependencies: '@types/json-schema': 7.0.15 - ajv: 8.18.0 - ajv-formats: 2.1.1(ajv@8.18.0) - ajv-keywords: 5.1.0(ajv@8.18.0) + ajv: 8.20.0 + ajv-formats: 2.1.1(ajv@8.20.0) + ajv-keywords: 5.1.0(ajv@8.20.0) search-insights@2.17.3: {} @@ -24499,18 +24956,18 @@ snapshots: select-hose@2.0.0: {} - selfsigned@2.4.1: + selfsigned@5.5.0: dependencies: - '@types/node-forge': 1.3.14 - node-forge: 1.3.3 + '@peculiar/x509': 1.14.3 + pkijs: 3.4.0 semver-diff@4.0.0: dependencies: - semver: 7.7.4 + semver: 7.8.0 semver@6.3.1: {} - semver@7.7.4: {} + semver@7.8.0: {} send@0.19.2: dependencies: @@ -24560,13 +25017,13 @@ snapshots: path-to-regexp: 3.3.0 range-parser: 1.2.0 - serve-index@1.9.1: + serve-index@1.9.2: dependencies: accepts: 1.3.8 batch: 0.6.1 debug: 2.6.9 escape-html: 1.0.3 - http-errors: 1.6.3 + http-errors: 1.8.1 mime-types: 2.1.35 parseurl: 1.3.3 transitivePeerDependencies: @@ -24603,8 +25060,6 @@ snapshots: gopd: 1.2.0 has-property-descriptors: 1.0.2 - setprototypeof@1.1.0: {} - setprototypeof@1.2.0: {} shallow-clone@3.0.1: @@ -24624,11 +25079,11 @@ snapshots: sharp@0.34.5: dependencies: - '@img/colour': 1.0.0 + '@img/colour': 1.1.0 detect-libc: 2.1.2 - node-addon-api: 8.5.0 - node-gyp: 12.2.0 - semver: 7.7.4 + node-addon-api: 8.7.0 + node-gyp: 12.3.0 + semver: 7.8.0 optionalDependencies: '@img/sharp-darwin-arm64': 0.34.5 '@img/sharp-darwin-x64': 0.34.5 @@ -24654,8 +25109,6 @@ snapshots: '@img/sharp-win32-arm64': 0.34.5 '@img/sharp-win32-ia32': 0.34.5 '@img/sharp-win32-x64': 0.34.5 - transitivePeerDependencies: - - supports-color shebang-command@2.0.0: dependencies: @@ -24665,7 +25118,7 @@ snapshots: shell-quote@1.8.3: {} - side-channel-list@1.0.0: + side-channel-list@1.0.1: dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 @@ -24689,7 +25142,7 @@ snapshots: dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 - side-channel-list: 1.0.0 + side-channel-list: 1.0.1 side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 @@ -24709,7 +25162,7 @@ snapshots: simple-concat: 1.0.1 optional: true - simple-icons@16.17.0: {} + simple-icons@16.20.0: {} sirv@2.0.4: dependencies: @@ -24725,12 +25178,12 @@ snapshots: sisteransi@1.0.5: {} - sitemap@7.1.2: + sitemap@7.1.3: dependencies: '@types/node': 17.0.45 '@types/sax': 1.2.7 arg: 5.0.2 - sax: 1.4.3 + sax: 1.6.0 skin-tone@2.0.0: dependencies: @@ -24742,17 +25195,15 @@ snapshots: slice-source@0.4.1: {} - smart-buffer@4.2.0: {} - snake-case@3.0.4: dependencies: dot-case: 3.0.4 tslib: 2.8.1 - socket.io-adapter@2.5.6: + socket.io-adapter@2.5.7: dependencies: debug: 4.4.3 - ws: 8.18.3 + ws: 8.20.1 transitivePeerDependencies: - bufferutil - supports-color @@ -24762,14 +25213,14 @@ snapshots: dependencies: '@socket.io/component-emitter': 3.1.2 debug: 4.4.3 - engine.io-client: 6.6.4 - socket.io-parser: 4.2.5 + engine.io-client: 6.6.5 + socket.io-parser: 4.2.6 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - socket.io-parser@4.2.5: + socket.io-parser@4.2.6: dependencies: '@socket.io/component-emitter': 3.1.2 debug: 4.4.3 @@ -24782,9 +25233,9 @@ snapshots: base64id: 2.0.0 cors: 2.8.6 debug: 4.4.3 - engine.io: 6.6.5 - socket.io-adapter: 2.5.6 - socket.io-parser: 4.2.5 + engine.io: 6.6.8 + socket.io-adapter: 2.5.7 + socket.io-parser: 4.2.6 transitivePeerDependencies: - bufferutil - supports-color @@ -24796,19 +25247,6 @@ snapshots: uuid: 8.3.2 websocket-driver: 0.7.4 - socks-proxy-agent@8.0.5: - dependencies: - agent-base: 7.1.4 - debug: 4.4.3 - socks: 2.8.7 - transitivePeerDependencies: - - supports-color - - socks@2.8.7: - dependencies: - ip-address: 10.1.0 - smart-buffer: 4.2.0 - sort-css-media-queries@2.2.0: {} source-map-js@1.2.1: {} @@ -24855,7 +25293,7 @@ snapshots: sprintf-js@1.0.3: {} - sql-formatter@15.7.3: + sql-formatter@15.8.0: dependencies: argparse: 2.0.1 nearley: 2.20.1 @@ -24873,11 +25311,7 @@ snapshots: bcrypt-pbkdf: 1.0.2 optionalDependencies: cpu-features: 0.0.10 - nan: 2.26.2 - - ssri@13.0.1: - dependencies: - minipass: 7.1.3 + nan: 2.27.0 stackback@0.0.2: {} @@ -24921,7 +25355,7 @@ snapshots: string-width@7.2.0: dependencies: emoji-regex: 10.6.0 - get-east-asian-width: 1.5.0 + get-east-asian-width: 1.6.0 strip-ansi: 7.2.0 string_decoder@1.1.1: @@ -24991,13 +25425,13 @@ snapshots: dependencies: inline-style-parser: 0.2.7 - stylehacks@6.1.1(postcss@8.5.12): + stylehacks@6.1.1(postcss@8.5.15): dependencies: browserslist: 4.28.2 - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 6.1.2 - stylis@4.3.6: {} + stylis@4.4.0: {} sucrase@3.35.1: dependencies: @@ -25019,13 +25453,13 @@ snapshots: formidable: 3.5.4 methods: 1.1.2 mime: 2.6.0 - qs: 6.14.1 + qs: 6.15.2 transitivePeerDependencies: - supports-color supercluster@8.0.1: dependencies: - kdbush: 4.0.2 + kdbush: 4.1.0 supertest@7.2.2: dependencies: @@ -25045,33 +25479,33 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-awesome@3.3.5(svelte@5.55.2): + svelte-awesome@3.3.5(svelte@5.55.8(@typescript-eslint/types@8.59.4)): dependencies: - svelte: 5.55.2 + svelte: 5.55.8(@typescript-eslint/types@8.59.4) - svelte-check@4.4.6(picomatch@4.0.4)(svelte@5.55.2)(typescript@6.0.3): + svelte-check@4.4.8(picomatch@4.0.4)(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3): dependencies: '@jridgewell/trace-mapping': 0.3.31 chokidar: 4.0.3 fdir: 6.5.0(picomatch@4.0.4) picocolors: 1.1.1 sade: 1.8.1 - svelte: 5.55.2 + svelte: 5.55.8(@typescript-eslint/types@8.59.4) typescript: 6.0.3 transitivePeerDependencies: - picomatch - svelte-eslint-parser@1.6.0(svelte@5.55.2): + svelte-eslint-parser@1.6.1(svelte@5.55.8(@typescript-eslint/types@8.59.4)): dependencies: eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 espree: 10.4.0 - postcss: 8.5.12 - postcss-scss: 4.0.9(postcss@8.5.12) + postcss: 8.5.15 + postcss-scss: 4.0.9(postcss@8.5.15) postcss-selector-parser: 7.1.1 - semver: 7.7.4 + semver: 7.8.0 optionalDependencies: - svelte: 5.55.2 + svelte: 5.55.8(@typescript-eslint/types@8.59.4) svelte-floating-ui@1.5.8: dependencies: @@ -25084,7 +25518,7 @@ snapshots: dependencies: highlight.js: 11.11.1 - svelte-i18n@4.0.1(svelte@5.55.2): + svelte-i18n@4.0.1(svelte@5.55.8(@typescript-eslint/types@8.59.4)): dependencies: cli-color: 2.0.4 deepmerge: 4.3.1 @@ -25092,26 +25526,26 @@ snapshots: estree-walker: 2.0.2 intl-messageformat: 10.7.18 sade: 1.8.1 - svelte: 5.55.2 + svelte: 5.55.8(@typescript-eslint/types@8.59.4) tiny-glob: 0.2.9 - svelte-jsoneditor@3.12.0(svelte@5.55.2): + svelte-jsoneditor@3.12.0(svelte@5.55.8(@typescript-eslint/types@8.59.4)): dependencies: - '@codemirror/autocomplete': 6.20.1 + '@codemirror/autocomplete': 6.20.2 '@codemirror/commands': 6.10.3 '@codemirror/lang-json': 6.0.2 '@codemirror/language': 6.12.3 - '@codemirror/lint': 6.9.5 + '@codemirror/lint': 6.9.6 '@codemirror/search': 6.7.0 '@codemirror/state': 6.6.0 - '@codemirror/view': 6.41.1 + '@codemirror/view': 6.43.0 '@fortawesome/free-regular-svg-icons': 7.2.0 '@fortawesome/free-solid-svg-icons': 7.2.0 '@jsonquerylang/jsonquery': 5.1.1 '@lezer/highlight': 1.2.3 - '@replit/codemirror-indentation-markers': 6.5.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.41.1) - ajv: 8.18.0 - codemirror-wrapped-line-indent: 1.0.9(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.41.1) + '@replit/codemirror-indentation-markers': 6.5.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.43.0) + ajv: 8.20.0 + codemirror-wrapped-line-indent: 1.0.9(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.43.0) diff-sequences: 29.6.3 immutable-json-patch: 6.0.2 jmespath: 0.16.0 @@ -25122,82 +25556,78 @@ snapshots: memoize-one: 6.0.0 natural-compare-lite: 1.4.0 sass: 1.99.0 - svelte: 5.55.2 - svelte-awesome: 3.3.5(svelte@5.55.2) + svelte: 5.55.8(@typescript-eslint/types@8.59.4) + svelte-awesome: 3.3.5(svelte@5.55.8(@typescript-eslint/types@8.59.4)) svelte-select: 5.8.3 vanilla-picker: 2.12.3 - svelte-maplibre@1.3.0(svelte@5.55.2): + svelte-maplibre@1.3.0(svelte@5.55.8(@typescript-eslint/types@8.59.4)): dependencies: d3-geo: 3.1.1 dequal: 2.0.3 just-compare: 2.3.0 maplibre-gl: 5.24.0 pmtiles: 3.2.1 - svelte: 5.55.2 + svelte: 5.55.8(@typescript-eslint/types@8.59.4) - svelte-parse-markup@0.1.5(svelte@5.55.2): + svelte-parse-markup@0.1.5(svelte@5.55.8(@typescript-eslint/types@8.59.4)): dependencies: - svelte: 5.55.2 + svelte: 5.55.8(@typescript-eslint/types@8.59.4) - svelte-persisted-store@0.12.0(svelte@5.55.2): + svelte-persisted-store@0.12.0(svelte@5.55.8(@typescript-eslint/types@8.59.4)): dependencies: - svelte: 5.55.2 + svelte: 5.55.8(@typescript-eslint/types@8.59.4) svelte-select@5.8.3: dependencies: svelte-floating-ui: 1.5.8 - svelte-toolbelt@0.10.6(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2): + svelte-toolbelt@0.10.6(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4)): dependencies: clsx: 2.1.1 - runed: 0.35.1(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2) + runed: 0.35.1(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4)) style-to-object: 1.0.14 - svelte: 5.55.2 + svelte: 5.55.8(@typescript-eslint/types@8.59.4) transitivePeerDependencies: - '@sveltejs/kit' - svelte@5.55.2: + svelte@5.55.8(@typescript-eslint/types@8.59.4): dependencies: '@jridgewell/remapping': 2.3.5 '@jridgewell/sourcemap-codec': 1.5.5 - '@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0) - '@types/estree': 1.0.8 + '@sveltejs/acorn-typescript': 1.0.10(acorn@8.16.0) + '@types/estree': 1.0.9 '@types/trusted-types': 2.0.7 acorn: 8.16.0 aria-query: 5.3.1 axobject-query: 4.1.0 clsx: 2.1.1 - devalue: 5.7.1 + devalue: 5.8.1 esm-env: 1.2.2 - esrap: 2.2.4 + esrap: 2.2.9(@typescript-eslint/types@8.59.4) is-reference: 3.0.3 locate-character: 3.0.0 magic-string: 0.30.21 zimmerframe: 1.1.4 + transitivePeerDependencies: + - '@typescript-eslint/types' svg-parser@2.0.4: {} - svgo@3.3.2: + svgo@3.3.3: dependencies: - '@trysound/sax': 0.2.0 commander: 7.2.0 css-select: 5.2.2 css-tree: 2.3.1 css-what: 6.2.2 csso: 5.0.5 picocolors: 1.1.1 + sax: 1.6.0 - swagger-ui-dist@5.32.4: + swagger-ui-dist@5.32.6: dependencies: '@scarf/scarf': 1.4.0 - swr@2.3.8(react@19.2.5): - dependencies: - dequal: 2.0.3 - react: 19.2.5 - use-sync-external-store: 1.6.0(react@19.2.5) - symbol-observable@4.0.0: {} symbol-tree@3.2.4: @@ -25207,7 +25637,7 @@ snapshots: dependencies: '@pkgr/core': 0.2.9 - systeminformation@5.31.5: {} + systeminformation@5.31.6: {} tabbable@6.4.0: {} @@ -25215,29 +25645,29 @@ snapshots: tailwind-csstree@0.3.1: {} - tailwind-merge@3.5.0: {} + tailwind-merge@3.6.0: {} - tailwind-variants@3.2.2(tailwind-merge@3.5.0)(tailwindcss@4.2.4): + tailwind-variants@3.2.2(tailwind-merge@3.6.0)(tailwindcss@4.3.0): dependencies: - tailwindcss: 4.2.4 + tailwindcss: 4.3.0 optionalDependencies: - tailwind-merge: 3.5.0 + tailwind-merge: 3.6.0 - tailwindcss-email-variants@3.0.5(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3)): + tailwindcss-email-variants@3.0.5(tailwindcss@3.4.19(tsx@4.22.3)(yaml@2.9.0)): dependencies: - tailwindcss: 3.4.19(tsx@4.21.0)(yaml@2.8.3) + tailwindcss: 3.4.19(tsx@4.22.3)(yaml@2.9.0) - tailwindcss-mso@2.0.3(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3)): + tailwindcss-mso@2.0.3(tailwindcss@3.4.19(tsx@4.22.3)(yaml@2.9.0)): dependencies: - tailwindcss: 3.4.19(tsx@4.21.0)(yaml@2.8.3) + tailwindcss: 3.4.19(tsx@4.22.3)(yaml@2.9.0) - tailwindcss-preset-email@1.4.1(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3)): + tailwindcss-preset-email@1.4.1(tailwindcss@3.4.19(tsx@4.22.3)(yaml@2.9.0)): dependencies: - tailwindcss: 3.4.19(tsx@4.21.0)(yaml@2.8.3) - tailwindcss-email-variants: 3.0.5(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3)) - tailwindcss-mso: 2.0.3(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3)) + tailwindcss: 3.4.19(tsx@4.22.3)(yaml@2.9.0) + tailwindcss-email-variants: 3.0.5(tailwindcss@3.4.19(tsx@4.22.3)(yaml@2.9.0)) + tailwindcss-mso: 2.0.3(tailwindcss@3.4.19(tsx@4.22.3)(yaml@2.9.0)) - tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3): + tailwindcss@3.4.19(tsx@4.22.3)(yaml@2.9.0): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -25253,19 +25683,19 @@ snapshots: normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.1.1 - postcss: 8.5.12 - postcss-import: 15.1.0(postcss@8.5.12) - postcss-js: 4.1.0(postcss@8.5.12) - postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.12)(tsx@4.21.0)(yaml@2.8.3) - postcss-nested: 6.2.0(postcss@8.5.12) + postcss: 8.5.15 + postcss-import: 15.1.0(postcss@8.5.15) + postcss-js: 4.1.0(postcss@8.5.15) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.15)(tsx@4.22.3)(yaml@2.9.0) + postcss-nested: 6.2.0(postcss@8.5.15) postcss-selector-parser: 6.1.2 - resolve: 1.22.11 + resolve: 1.22.12 sucrase: 3.35.1 transitivePeerDependencies: - tsx - yaml - tailwindcss@4.2.4: {} + tailwindcss@4.3.0: {} tapable@2.3.3: {} @@ -25279,7 +25709,7 @@ snapshots: tar-fs@3.1.2: dependencies: pump: 3.0.4 - tar-stream: 3.1.8 + tar-stream: 3.2.0 optionalDependencies: bare-fs: 4.7.1 bare-path: 3.0.0 @@ -25296,9 +25726,9 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - tar-stream@3.1.8: + tar-stream@3.2.0: dependencies: - b4a: 1.8.0 + b4a: 1.8.1 bare-fs: 4.7.1 fast-fifo: 1.3.2 streamx: 2.25.0 @@ -25316,7 +25746,7 @@ snapshots: mkdirp: 1.0.4 yallist: 4.0.0 - tar@7.5.7: + tar@7.5.15: dependencies: '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 @@ -25331,26 +25761,42 @@ snapshots: - bare-abort-controller - react-native-b4a - terser-webpack-plugin@5.4.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0)(webpack@5.106.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0)): + terser-webpack-plugin@5.6.0(@swc/core@1.15.33(@swc/helpers@0.5.21))(esbuild@0.28.0)(lightningcss@1.32.0)(webpack@5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.21))(esbuild@0.28.0)(lightningcss@1.32.0)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 - terser: 5.46.1 - webpack: 5.106.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0) + terser: 5.47.1 + webpack: 5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.21))(esbuild@0.28.0)(lightningcss@1.32.0) optionalDependencies: - '@swc/core': 1.15.30(@swc/helpers@0.5.21) + '@swc/core': 1.15.33(@swc/helpers@0.5.21) esbuild: 0.28.0 + lightningcss: 1.32.0 - terser-webpack-plugin@5.4.0(webpack@5.106.2): + terser-webpack-plugin@5.6.0(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(webpack@5.107.0(postcss@8.5.15)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 - terser: 5.46.1 - webpack: 5.106.2 + terser: 5.47.1 + webpack: 5.107.0(postcss@8.5.15) + optionalDependencies: + clean-css: 5.3.3 + cssnano: 6.1.2(postcss@8.5.15) + html-minifier-terser: 7.2.0 + postcss: 8.5.15 - terser@5.46.1: + terser-webpack-plugin@5.6.0(postcss@8.5.15)(webpack@5.107.0(postcss@8.5.15)): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + jest-worker: 27.5.1 + schema-utils: 4.3.3 + terser: 5.47.1 + webpack: 5.107.0(postcss@8.5.15) + optionalDependencies: + postcss: 8.5.15 + + terser@5.47.1: dependencies: '@jridgewell/source-map': 0.3.11 acorn: 8.16.0 @@ -25372,7 +25818,7 @@ snapshots: byline: 5.0.0 debug: 4.4.3 docker-compose: 1.4.2 - dockerode: 4.0.10 + dockerode: 4.0.12 get-port: 7.2.0 proper-lockfile: 4.1.2 properties-reader: 3.0.1 @@ -25388,7 +25834,7 @@ snapshots: text-decoder@1.2.7: dependencies: - b4a: 1.8.0 + b4a: 1.8.1 transitivePeerDependencies: - react-native-b4a @@ -25402,15 +25848,13 @@ snapshots: dependencies: any-promise: 1.3.0 - thingies@2.5.0(tslib@2.8.1): + thingies@2.6.0(tslib@2.8.1): dependencies: tslib: 2.8.1 three@0.179.1: {} - three@0.182.0: {} - - throttleit@2.1.0: {} + three@0.184.0: {} through@2.3.8: {} @@ -25436,7 +25880,7 @@ snapshots: tinyexec@0.3.2: {} - tinyexec@1.1.1: {} + tinyexec@1.1.2: {} tinyglobby@0.2.16: dependencies: @@ -25518,6 +25962,16 @@ snapshots: ts-interface-checker@0.1.13: {} + tsc-alias@1.8.17: + dependencies: + chokidar: 3.6.0 + commander: 9.5.0 + get-tsconfig: 4.14.0 + globby: 11.1.0 + mylas: 2.1.14 + normalize-path: 3.0.0 + plimit-lit: 1.6.1 + tsconfck@3.1.6(typescript@6.0.3): optionalDependencies: typescript: 6.0.3 @@ -25525,7 +25979,7 @@ snapshots: tsconfig-paths-webpack-plugin@4.2.0: dependencies: chalk: 4.1.2 - enhanced-resolve: 5.21.0 + enhanced-resolve: 5.21.5 tapable: 2.3.3 tsconfig-paths: 4.2.0 @@ -25535,17 +25989,22 @@ snapshots: minimist: 1.2.8 strip-bom: 3.0.0 + tslib@1.14.1: {} + tslib@2.8.1: {} tsscmp@1.0.6: {} - tsx@4.21.0: + tsx@4.22.3: dependencies: - esbuild: 0.27.4 - get-tsconfig: 4.13.0 + esbuild: 0.28.0 optionalDependencies: fsevents: 2.3.3 + tsyringe@4.10.0: + dependencies: + tslib: 1.14.1 + tunnel-agent@0.6.0: dependencies: safe-buffer: 5.2.1 @@ -25572,9 +26031,9 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 - type-is@2.0.1: + type-is@2.1.0: dependencies: - content-type: 1.0.5 + content-type: 2.0.0 media-typer: 1.1.0 mime-types: 3.0.2 @@ -25586,13 +26045,13 @@ snapshots: typedarray@0.0.6: {} - typescript-eslint@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3): + typescript-eslint@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.59.0(@typescript-eslint/parser@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/parser': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) - eslint: 10.2.1(jiti@2.6.1) + '@typescript-eslint/eslint-plugin': 8.59.4(@typescript-eslint/parser@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.59.4(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.4.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -25609,8 +26068,6 @@ snapshots: is-standalone-pwa: 0.1.1 ua-is-frozen: 0.1.2 - ufo@1.6.2: {} - uglify-js@3.19.3: optional: true @@ -25626,8 +26083,7 @@ snapshots: undici-types@7.16.0: {} - undici-types@7.19.2: - optional: true + undici@6.25.0: {} undici@7.25.0: {} @@ -25664,14 +26120,6 @@ snapshots: trough: 1.0.5 vfile: 4.2.1 - unique-filename@5.0.0: - dependencies: - unique-slug: 6.0.0 - - unique-slug@6.0.0: - dependencies: - imurmurhash: 0.1.4 - unique-string@3.0.0: dependencies: crypto-random-string: 4.0.0 @@ -25718,7 +26166,7 @@ snapshots: unist-util-is: 4.1.0 unist-util-visit-parents: 3.1.1 - unist-util-visit@5.0.0: + unist-util-visit@5.1.0: dependencies: '@types/unist': 3.0.3 unist-util-is: 6.0.1 @@ -25728,10 +26176,10 @@ snapshots: unpipe@1.0.0: {} - unplugin-swc@1.5.9(@swc/core@1.15.30(@swc/helpers@0.5.21))(rollup@4.55.1): + unplugin-swc@1.5.9(@swc/core@1.15.33(@swc/helpers@0.5.21))(rollup@4.60.4): dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.55.1) - '@swc/core': 1.15.30(@swc/helpers@0.5.21) + '@rollup/pluginutils': 5.3.0(rollup@4.60.4) + '@swc/core': 1.15.33(@swc/helpers@0.5.21) load-tsconfig: 0.2.5 unplugin: 2.3.11 transitivePeerDependencies: @@ -25763,7 +26211,7 @@ snapshots: is-yarn-global: 0.4.1 latest-version: 7.0.0 pupa: 3.3.0 - semver: 7.7.4 + semver: 7.8.0 semver-diff: 4.0.0 xdg-basedir: 5.1.0 @@ -25773,26 +26221,22 @@ snapshots: dependencies: punycode: 2.3.1 - url-loader@4.1.1(file-loader@6.2.0(webpack@5.106.2))(webpack@5.106.2): + url-loader@4.1.1(file-loader@6.2.0(webpack@5.107.0(postcss@8.5.15)))(webpack@5.107.0(postcss@8.5.15)): dependencies: loader-utils: 2.0.4 mime-types: 2.1.35 schema-utils: 3.3.0 - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) optionalDependencies: - file-loader: 6.2.0(webpack@5.106.2) + file-loader: 6.2.0(webpack@5.107.0(postcss@8.5.15)) url@0.11.4: dependencies: punycode: 1.4.1 - qs: 6.14.1 + qs: 6.15.2 urlpattern-polyfill@8.0.2: {} - use-sync-external-store@1.6.0(react@19.2.5): - dependencies: - react: 19.2.5 - utf8-byte-length@1.0.5: {} util-deprecate@1.0.2: {} @@ -25803,9 +26247,9 @@ snapshots: utils-merge@1.0.1: {} - utimes@5.2.1(encoding@0.1.13): + utimes@5.2.1: dependencies: - '@mapbox/node-pre-gyp': 1.0.11(encoding@0.1.13) + '@mapbox/node-pre-gyp': 1.0.11 node-addon-api: 4.3.0 transitivePeerDependencies: - encoding @@ -25813,13 +26257,11 @@ snapshots: uuid@10.0.0: {} - uuid@11.1.0: {} - uuid@14.0.0: {} uuid@8.3.2: {} - valibot@1.3.1(typescript@6.0.3): + valibot@1.4.0(typescript@6.0.3): optionalDependencies: typescript: 6.0.3 @@ -25862,22 +26304,21 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-imagetools@9.0.3(rollup@4.55.1): + vite-imagetools@9.0.3(rollup@4.60.4): dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.55.1) + '@rollup/pluginutils': 5.3.0(rollup@4.60.4) imagetools-core: 9.1.0 sharp: 0.34.5 transitivePeerDependencies: - rollup - - supports-color - vite-node@3.2.4(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): + vite-node@3.2.4(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 7.3.3(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) transitivePeerDependencies: - '@types/node' - jiti @@ -25892,81 +26333,64 @@ snapshots: - tsx - yaml - vite-tsconfig-paths@6.1.1(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + vite-tsconfig-paths@6.1.1(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@6.0.3) - vite: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) transitivePeerDependencies: - supports-color - typescript - vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): + vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0): dependencies: - esbuild: 0.27.4 + esbuild: 0.27.7 fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 - postcss: 8.5.12 - rollup: 4.55.1 + postcss: 8.5.15 + rollup: 4.60.4 tinyglobby: 0.2.16 optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 fsevents: 2.3.3 - jiti: 2.6.1 + jiti: 2.7.0 lightningcss: 1.32.0 sass: 1.99.0 - terser: 5.46.1 - tsx: 4.21.0 - yaml: 2.8.3 + terser: 5.47.1 + tsx: 4.22.3 + yaml: 2.9.0 - vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): + vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 - postcss: 8.5.12 - rolldown: 1.0.0-rc.17 + postcss: 8.5.15 + rolldown: 1.0.1 tinyglobby: 0.2.16 optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 esbuild: 0.28.0 fsevents: 2.3.3 - jiti: 2.6.1 + jiti: 2.7.0 sass: 1.99.0 - terser: 5.46.1 - tsx: 4.21.0 - yaml: 2.8.3 + terser: 5.47.1 + tsx: 4.22.3 + yaml: 2.9.0 - vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): - dependencies: - lightningcss: 1.32.0 - picomatch: 4.0.4 - postcss: 8.5.12 - rolldown: 1.0.0-rc.17 - tinyglobby: 0.2.16 + vitefu@1.1.3(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)): optionalDependencies: - '@types/node': 25.6.0 - esbuild: 0.28.0 - fsevents: 2.3.3 - jiti: 2.6.1 - sass: 1.99.0 - terser: 5.46.1 - tsx: 4.21.0 - yaml: 2.8.3 + vite: 8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) - vitefu@1.1.2(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): - optionalDependencies: - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - - vitest-fetch-mock@0.4.5(vitest@4.1.5): + vitest-fetch-mock@0.4.5(vitest@4.1.7): dependencies: - vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@3.2.3))(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@24.12.4)(@vitest/coverage-v8@4.1.7)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@3.2.3))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) - vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.12.2)(happy-dom@20.9.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@3.2.3))(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): + vitest@3.2.4(@types/debug@4.1.13)(@types/node@24.12.4)(happy-dom@20.9.0)(jiti@2.7.0)(jsdom@26.1.0(canvas@3.2.3))(lightningcss@1.32.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/mocker': 3.2.4(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -25984,12 +26408,12 @@ snapshots: tinyglobby: 0.2.16 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - vite-node: 3.2.4(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 7.3.3(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) + vite-node: 3.2.4(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/debug': 4.1.12 - '@types/node': 24.12.2 + '@types/debug': 4.1.13 + '@types/node': 24.12.4 happy-dom: 20.9.0 jsdom: 26.1.0(canvas@3.2.3) transitivePeerDependencies: @@ -26006,15 +26430,15 @@ snapshots: - tsx - yaml - vitest@4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@3.2.3))(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@24.12.4)(@vitest/coverage-v8@4.1.7)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@3.2.3))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)): dependencies: - '@vitest/expect': 4.1.5 - '@vitest/mocker': 4.1.5(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - '@vitest/pretty-format': 4.1.5 - '@vitest/runner': 4.1.5 - '@vitest/snapshot': 4.1.5 - '@vitest/spy': 4.1.5 - '@vitest/utils': 4.1.5 + '@vitest/expect': 4.1.7 + '@vitest/mocker': 4.1.7(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) + '@vitest/pretty-format': 4.1.7 + '@vitest/runner': 4.1.7 + '@vitest/snapshot': 4.1.7 + '@vitest/spy': 4.1.7 + '@vitest/utils': 4.1.7 es-module-lexer: 2.1.0 expect-type: 1.3.0 magic-string: 0.30.21 @@ -26023,68 +26447,20 @@ snapshots: picomatch: 4.0.4 std-env: 4.1.0 tinybench: 2.9.0 - tinyexec: 1.1.1 + tinyexec: 1.1.2 tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vite: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.1 - '@types/node': 24.12.2 - '@vitest/coverage-v8': 4.1.5(vitest@4.1.5) + '@types/node': 24.12.4 + '@vitest/coverage-v8': 4.1.7(vitest@4.1.7) happy-dom: 20.9.0 jsdom: 26.1.0(canvas@3.2.3) transitivePeerDependencies: - msw - vitest@4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@3.2.3))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): - dependencies: - '@vitest/expect': 4.1.5 - '@vitest/mocker': 4.1.5(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - '@vitest/pretty-format': 4.1.5 - '@vitest/runner': 4.1.5 - '@vitest/snapshot': 4.1.5 - '@vitest/spy': 4.1.5 - '@vitest/utils': 4.1.5 - es-module-lexer: 2.1.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.4 - std-env: 4.1.0 - tinybench: 2.9.0 - tinyexec: 1.1.1 - tinyglobby: 0.2.16 - tinyrainbow: 3.1.0 - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - why-is-node-running: 2.3.0 - optionalDependencies: - '@opentelemetry/api': 1.9.1 - '@types/node': 25.6.0 - '@vitest/coverage-v8': 4.1.5(vitest@4.1.5) - happy-dom: 20.9.0 - jsdom: 26.1.0(canvas@3.2.3) - transitivePeerDependencies: - - msw - - vscode-jsonrpc@8.2.0: {} - - vscode-languageserver-protocol@3.17.5: - dependencies: - vscode-jsonrpc: 8.2.0 - vscode-languageserver-types: 3.17.5 - - vscode-languageserver-textdocument@1.0.12: {} - - vscode-languageserver-types@3.17.5: {} - - vscode-languageserver@9.0.1: - dependencies: - vscode-languageserver-protocol: 3.17.5 - - vscode-uri@3.0.8: {} - w3c-keyname@2.2.8: {} w3c-xmlserializer@5.0.0: @@ -26118,7 +26494,7 @@ snapshots: dependencies: '@discoveryjs/json-ext': 0.5.7 acorn: 8.16.0 - acorn-walk: 8.3.4 + acorn-walk: 8.3.5 commander: 7.2.0 debounce: 1.2.1 escape-string-regexp: 4.0.0 @@ -26132,7 +26508,7 @@ snapshots: - bufferutil - utf-8-validate - webpack-dev-middleware@7.4.5(tslib@2.8.1)(webpack@5.106.2): + webpack-dev-middleware@7.4.5(tslib@2.8.1)(webpack@5.107.0(postcss@8.5.15)): dependencies: colorette: 2.0.20 memfs: 4.57.2(tslib@2.8.1) @@ -26141,16 +26517,16 @@ snapshots: range-parser: 1.2.1 schema-utils: 4.3.3 optionalDependencies: - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) transitivePeerDependencies: - tslib - webpack-dev-server@5.2.2(tslib@2.8.1)(webpack@5.106.2): + webpack-dev-server@5.2.4(tslib@2.8.1)(webpack@5.107.0(postcss@8.5.15)): dependencies: '@types/bonjour': 3.5.13 '@types/connect-history-api-fallback': 1.5.4 '@types/express': 4.17.25 - '@types/express-serve-static-core': 4.19.7 + '@types/express-serve-static-core': 4.19.8 '@types/serve-index': 1.9.4 '@types/serve-static': 1.15.10 '@types/sockjs': 0.3.36 @@ -26161,22 +26537,22 @@ snapshots: colorette: 2.0.20 compression: 1.8.1 connect-history-api-fallback: 2.0.0 - express: 4.22.1 + express: 4.22.2 graceful-fs: 4.2.11 http-proxy-middleware: 2.0.9(@types/express@4.17.25) - ipaddr.js: 2.3.0 - launch-editor: 2.12.0 + ipaddr.js: 2.4.0 + launch-editor: 2.13.2 open: 10.2.0 p-retry: 6.2.1 schema-utils: 4.3.3 - selfsigned: 2.4.1 - serve-index: 1.9.1 + selfsigned: 5.5.0 + serve-index: 1.9.2 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 7.4.5(tslib@2.8.1)(webpack@5.106.2) - ws: 8.20.0 + webpack-dev-middleware: 7.4.5(tslib@2.8.1)(webpack@5.107.0(postcss@8.5.15)) + ws: 8.20.1 optionalDependencies: - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) transitivePeerDependencies: - bufferutil - debug @@ -26198,14 +26574,14 @@ snapshots: webpack-node-externals@3.0.0: {} - webpack-sources@3.3.4: {} + webpack-sources@3.4.1: {} webpack-virtual-modules@0.6.2: {} - webpack@5.106.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0): + webpack@5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.21))(esbuild@0.28.0)(lightningcss@1.32.0): dependencies: '@types/eslint-scope': 3.7.7 - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/json-schema': 7.0.15 '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/wasm-edit': 1.14.1 @@ -26214,30 +26590,38 @@ snapshots: acorn-import-phases: 1.0.4(acorn@8.16.0) browserslist: 4.28.2 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.21.0 + enhanced-resolve: 5.21.5 es-module-lexer: 2.1.0 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 json-parse-even-better-errors: 2.3.1 - loader-runner: 4.3.1 + loader-runner: 4.3.2 mime-types: 2.1.35 neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.3 - terser-webpack-plugin: 5.4.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0)(webpack@5.106.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0)) + terser-webpack-plugin: 5.6.0(@swc/core@1.15.33(@swc/helpers@0.5.21))(esbuild@0.28.0)(lightningcss@1.32.0)(webpack@5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.21))(esbuild@0.28.0)(lightningcss@1.32.0)) watchpack: 2.5.1 - webpack-sources: 3.3.4 + webpack-sources: 3.4.1 transitivePeerDependencies: + - '@minify-html/node' - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso - esbuild + - html-minifier-terser + - lightningcss + - postcss - uglify-js - webpack@5.106.2: + webpack@5.107.0(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15): dependencies: - '@types/eslint-scope': 3.7.7 - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/json-schema': 7.0.15 '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/wasm-edit': 1.14.1 @@ -26246,33 +26630,81 @@ snapshots: acorn-import-phases: 1.0.4(acorn@8.16.0) browserslist: 4.28.2 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.21.0 + enhanced-resolve: 5.21.5 es-module-lexer: 2.1.0 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 - loader-runner: 4.3.1 + loader-runner: 4.3.2 mime-db: 1.54.0 neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.3 - terser-webpack-plugin: 5.4.0(webpack@5.106.2) + terser-webpack-plugin: 5.6.0(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(webpack@5.107.0(postcss@8.5.15)) watchpack: 2.5.1 - webpack-sources: 3.3.4 + webpack-sources: 3.4.1 transitivePeerDependencies: + - '@minify-html/node' - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso - esbuild + - html-minifier-terser + - lightningcss + - postcss - uglify-js - webpackbar@7.0.0(webpack@5.106.2): + webpack@5.107.0(postcss@8.5.15): + dependencies: + '@types/estree': 1.0.9 + '@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.16.0 + acorn-import-phases: 1.0.4(acorn@8.16.0) + browserslist: 4.28.2 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.21.5 + es-module-lexer: 2.1.0 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + loader-runner: 4.3.2 + mime-db: 1.54.0 + neo-async: 2.6.2 + schema-utils: 4.3.3 + tapable: 2.3.3 + terser-webpack-plugin: 5.6.0(postcss@8.5.15)(webpack@5.107.0(postcss@8.5.15)) + watchpack: 2.5.1 + webpack-sources: 3.4.1 + transitivePeerDependencies: + - '@minify-html/node' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - uglify-js + + webpackbar@7.0.0(webpack@5.107.0(postcss@8.5.15)): dependencies: ansis: 3.17.0 consola: 3.4.2 pretty-time: 1.1.0 std-env: 3.10.0 optionalDependencies: - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) websocket-driver@0.7.4: dependencies: @@ -26373,9 +26805,7 @@ snapshots: ws@7.5.10: {} - ws@8.18.3: {} - - ws@8.20.0: {} + ws@8.20.1: {} wsl-utils@0.1.0: dependencies: @@ -26390,7 +26820,7 @@ snapshots: xml-js@1.6.11: dependencies: - sax: 1.4.3 + sax: 1.6.0 xml-name-validator@5.0.0: optional: true @@ -26414,7 +26844,7 @@ snapshots: yaml@1.10.3: {} - yaml@2.8.3: {} + yaml@2.9.0: {} yargs-parser@18.1.3: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 57aeb9c7bf..f53cb0d406 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -7,6 +7,7 @@ packages: - plugins - web - .github + - packages/* ignoredBuiltDependencies: - '@nestjs/core' - '@parcel/watcher' diff --git a/readme_i18n/README_ar_JO.md b/readme_i18n/README_ar_JO.md index e0e13eeaf6..860ba24eaf 100644 --- a/readme_i18n/README_ar_JO.md +++ b/readme_i18n/README_ar_JO.md @@ -37,29 +37,24 @@ ภาษาไทย

-## تنصل +> [!WARNING] +> ⚠️ اتبع دائمًا خطة النسخ الاحتياطي [١-٢-٣](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) لصورك ومقاطع الفيديو الثمينة الخاصة بك +> -- ⚠️ هذا التطبيق قيد التطوير النشط للغاية -- ⚠️ توقع الأخطاء والتغييرات العاجلة -- ⚠️ **لا تستخدم التطبيق باعتباره الطريقة الوحيدة لتخزين الصور ومقاطع الفيديو الخاصة بك** -- ⚠️ اتبع دائمًا خطة النسخ الاحتياطي [١-٢-٣](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) لصورك ومقاطع الفيديو الثمينة الخاصة بك +> [!NOTE] +> يمكنك العثور على الوثائق الرئيسية، بما في ذلك أدلة التثبيت، على https://immich.app/ +## روابط -## محتوى - -- [الوثائق الرسمية](https://docs.immich.app) -- [خريطة الطريق](https://github.com/orgs/immich-app/projects/1) -- [تجريبي](#demo) -- [سمات](#features) +- [الوثائق الرسمية](https://docs.immich.app/) - [مقدمة](https://docs.immich.app/overview/introduction) - [تعليمات التحميل](https://docs.immich.app/install/requirements) +- [خريطة الطريق](https://immich.app/roadmap) +- [تجريبي](#تجريبي) +- [سمات](#سمات) +- [الترجمات](https://docs.immich.app/developer/translations) - [قواعد المساهمة](https://docs.immich.app/overview/support-the-project) -## توثيق - -يمكنك العثور على الوثائق الرئيسية، بما في ذلك أدلة التثبيت، هنا -https://immich.app - ## تجريبي يمكنك الوصول إلى العرض التوضيحي على الويب على diff --git a/readme_i18n/README_ca_ES.md b/readme_i18n/README_ca_ES.md index d09362aa0f..49aebd9c10 100644 --- a/readme_i18n/README_ca_ES.md +++ b/readme_i18n/README_ca_ES.md @@ -37,26 +37,24 @@ ภาษาไทย

-## Avís legal +> [!WARNING] +> ⚠️ Always follow [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) backup plan for your precious photos and videos! +> -- ⚠️ El projecte està en desenvolupament **molt actiu**. -- ⚠️ Espereu errors i canvis que poden trencar coses. -- ⚠️ **No utilitzeu l'aplicació com a única manera de guardar les vostres fotos i vídeos!** +> [!NOTE] +> Podeu trobar la documentació principal, incloent les guies d'instal·lació, a https://immich.app/. ## Contingut -- [Documentació oficial](https://docs.immich.app) -- [Mapa de ruta](https://github.com/orgs/immich-app/projects/1) -- [Demo](#demo) -- [Funcionalitats](#funcionalitats) +- [Documentació](https://docs.immich.app/) - [Introducció](https://docs.immich.app/overview/introduction) - [Instal·lació](https://docs.immich.app/install/requirements) +- [Mapa de ruta](https://immich.app/roadmap) +- [Demo](#demo) +- [Funcionalitats](#funcionalitats) +- [Traduccions](https://docs.immich.app/developer/translations) - [Directrius de contribució](https://docs.immich.app/overview/support-the-project) -## Documentació - -Podeu trobar la documentació principal, incloent les guies d'instal·lació, a https://immich.app/. - ## Demo Podeu accedir a la demostració web a https://demo.immich.app. Per a l'aplicació mòbil, podeu utilitzar `https://demo.immich.app` com a "URL de punt final del servidor". diff --git a/readme_i18n/README_de_DE.md b/readme_i18n/README_de_DE.md index 488b05abcc..11f9e24686 100644 --- a/readme_i18n/README_de_DE.md +++ b/readme_i18n/README_de_DE.md @@ -38,7 +38,9 @@ ภาษาไทย

-- ⚠️ Befolge immer die [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) Backup-Regel für deine wertvollen Fotos und Videos! +> [!WARNING] +> ⚠️ Befolge immer die [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) Backup-Regel für deine wertvollen Fotos und Videos! +> > [!NOTE] > Die Hauptdokumentation, einschließlich der Installationsanleitungen, befinden sich unter https://immich.app/. @@ -49,7 +51,7 @@ - [Offizielle Dokumentation](https://docs.immich.app) - [Über Immich](https://docs.immich.app/overview/introduction) - [Installation](https://docs.immich.app/install/requirements) -- [Roadmap](https://github.com/orgs/immich-app/projects/1) +- [Roadmap](https://immich.app/roadmap) - [Demo](#demo) - [Funktionen](#funktionen) - [Übersetzungen](https://docs.immich.app/developer/translations) diff --git a/readme_i18n/README_es_ES.md b/readme_i18n/README_es_ES.md index 032f8c50a8..2aa4d16ecf 100644 --- a/readme_i18n/README_es_ES.md +++ b/readme_i18n/README_es_ES.md @@ -37,27 +37,24 @@ ภาษาไทย

-## Advertencia +> [!WARNING] +> ⚠️ Siempre sigue el plan de backups [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) para tus fotos y videos. +> -- ⚠️ El proyecto está en **activo desarrollo**. -- ⚠️ Es probable que haya errores y cambios disruptivos. -- ⚠️ **¡No utilices la aplicación como única forma de almacenar tus fotos y videos!** -- ⚠️ Siempre sigue el plan de backups [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) para tus fotos y videos. +> [!NOTE] +> Puedes encontrar la documentación oficial, incluidas las guías de instalación, en . ## Contenido -- [Documentación oficial](https://docs.immich.app) -- [Hoja de ruta](https://github.com/orgs/immich-app/projects/1) -- [Demo](#demo) -- [Funciones](#funciones) +- [Documentación](https://docs.immich.app/) - [Introducción](https://docs.immich.app/overview/introduction) - [Instalación](https://docs.immich.app/install/requirements) +- [Hoja de ruta](https://immich.app/roadmap) +- [Demo](#demo) +- [Funciones](#funciones) +- [Traducciones](https://docs.immich.app/developer/translations) - [Directrices para contribuir](https://docs.immich.app/overview/support-the-project) -## Documentación - -Puedes encontrar la documentación oficial, incluidas las guías de instalación, en . - ## Demo Puedes acceder a la demostración web en . Para la aplicación móvil, puedes usar `https://demo.immich.app` en la `URL del servidor`. diff --git a/readme_i18n/README_fr_FR.md b/readme_i18n/README_fr_FR.md index 349a0c49ce..e4fef66525 100644 --- a/readme_i18n/README_fr_FR.md +++ b/readme_i18n/README_fr_FR.md @@ -37,27 +37,24 @@ ภาษาไทย

-## Clause de non-responsabilité +> [!WARNING] +> ⚠️ Ayez toujours un plan de sauvegarde en [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) pour vos précieuses photos et vidéos ! +> -- ⚠️ Le projet est en **très fort** développement. -- ⚠️ Attendez-vous à rencontrer des bogues et des changements importants. -- ⚠️ **N'utilisez pas cette application comme seul support de sauvegarde de vos photos et vos vidéos.** -- ⚠️ Ayez toujours un plan de sauvegarde en [3-2-1](https://www.seagate.com/fr/fr/blog/what-is-a-3-2-1-backup-strategy/) pour vos précieuses photos et vidéos ! +> [!NOTE] +> Vous pouvez trouver la documentation principale ainsi que les guides d'installation sur https://immich.app/. ## Sommaire -- [Documentation officielle](https://docs.immich.app) -- [Feuille de route](https://github.com/orgs/immich-app/projects/1) -- [Démo](#démo) -- [Fonctionnalités](#fonctionnalités) +- [Documentation](https://docs.immich.app/) - [Introduction](https://docs.immich.app/overview/introduction) - [Installation](https://docs.immich.app/install/requirements) +- [Feuille de route](https://immich.app/roadmap) +- [Démo](#démo) +- [Fonctionnalités](#fonctionnalités) +- [Traductions](https://docs.immich.app/developer/translations) - [Contribution](https://docs.immich.app/overview/support-the-project) -## Documentation - -Vous pouvez trouver la documentation principale ainsi que les guides d'installation sur https://immich.app/. - ## Démo Vous pouvez accéder à la démo en ligne sur https://demo.immich.app. Pour l'application mobile, vous pouvez utiliser `https://demo.immich.app` dans le champ `URL du point d'accès au serveur` diff --git a/readme_i18n/README_it_IT.md b/readme_i18n/README_it_IT.md index 711840fd9d..5d8d8b0880 100644 --- a/readme_i18n/README_it_IT.md +++ b/readme_i18n/README_it_IT.md @@ -38,12 +38,9 @@ ภาษาไทย

-## Avvertenze - -- ⚠️ Il progetto è in fase di sviluppo **molto attivo**. -- ⚠️ Possono esserci bug o cambiamenti radicali, che possono non essere retrocompatibili (breaking changes). -- ⚠️ **Non usare l’app come unico modo per archiviare le tue foto e i tuoi video.** -- ⚠️ Segui sempre la regola di backup [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) per proteggere i tuoi ricordi e le foto a cui tieni! +> [!WARNING] +> ⚠️ Segui sempre la regola di backup [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) per proteggere i tuoi ricordi e le foto a cui tieni! +> > [!NOTE] > La documentazione principale, comprese le guide all’installazione, si trova su https://immich.app/. diff --git a/readme_i18n/README_ja_JP.md b/readme_i18n/README_ja_JP.md index 0e74077895..8c2088247a 100644 --- a/readme_i18n/README_ja_JP.md +++ b/readme_i18n/README_ja_JP.md @@ -36,27 +36,24 @@ ภาษาไทย

-## 免責事項 +> [!WARNING] +> ⚠️ 大切な写真やビデオは、常に [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) のバックアッププランに従ってください! +> -- ⚠️ このプロジェクトは **非常に活発に** 開発中です。 -- ⚠️ バグの存在や変更が入ることも予想されます。 -- ⚠️ **写真やビデオを保存する唯一の方法としてこのアプリを使用しないでください。** -- ⚠️ 大切な写真やビデオは、常に [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) のバックアッププランに従ってください! +> [!NOTE] +> インストールガイドを含む主なドキュメントは、https://immich.app/ です。 ## コンテンツ -- [公式ドキュメント](https://docs.immich.app) -- [ロードマップ](https://github.com/orgs/immich-app/projects/1) -- [デモ](#デモ) -- [機能](#機能) +- [公式ドキュメント](https://docs.immich.app/) - [紹介](https://docs.immich.app/overview/introduction) - [インストール](https://docs.immich.app/install/requirements) +- [ロードマップ](https://immich.app/roadmap) +- [デモ](#デモ) +- [機能](#機能) +- [翻訳](https://docs.immich.app/developer/translations) - [コントリビューションガイド](https://docs.immich.app/overview/support-the-project) -## ドキュメント - -インストールガイドを含む主なドキュメントは、https://immich.app/ です。 - ## デモ web デモは https://demo.immich.app からアクセスできます。モバイルアプリの場合、`Server Endpoint URL` には `https://demo.immich.app` を使用することができます diff --git a/readme_i18n/README_ko_KR.md b/readme_i18n/README_ko_KR.md index c2dfd11dd3..66ff4c79c3 100644 --- a/readme_i18n/README_ko_KR.md +++ b/readme_i18n/README_ko_KR.md @@ -39,12 +39,9 @@

-## 주의 사항 - -- ⚠️ 이 프로젝트는 **매우 활발하게** 개발 중입니다. -- ⚠️ 버그와 잦은 변경 사항이 있을 것으로 예상됩니다. -- ⚠️ **사진과 동영상을 이 앱에만 단독으로 저장하지 마세요.** -- ⚠️ 중요한 사진과 동영상을 위해 항상 [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) 백업 계획을 따르세요! +> [!WARNING] +> ⚠️ 중요한 사진과 동영상을 위해 항상 [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) 백업 계획을 따르세요! +> > [!NOTE] > 설치하는 방법을 포함한 주요 문서는 https://immich.app/ 에서 확인할 수 있습니다. @@ -57,7 +54,7 @@ - [로드맵](https://immich.app/roadmap) - [데모](#데모) - [기능](#기능) -- [번역](https://docs.immich.app/developer/tranlations) +- [번역](https://docs.immich.app/developer/translations) - [기여](https://docs.immich.app/overview/support-the-project) ## 데모 diff --git a/readme_i18n/README_nl_NL.md b/readme_i18n/README_nl_NL.md index ac72e9d238..ef7591b618 100644 --- a/readme_i18n/README_nl_NL.md +++ b/readme_i18n/README_nl_NL.md @@ -37,27 +37,24 @@ ภาษาไทย

-## Disclaimer +> [!WARNING] +> ⚠️ Volg altijd het [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) backup plan voor je kostbare foto's en video's! +> -- ⚠️ Het project wordt momenteel **zeer actief** ontwikkeld. -- ⚠️ Verwacht bugs en ingrijpende wijzigingen. -- ⚠️ **Gebruik de app niet als de enige manier om uw foto's en video's op te slaan.** -- ⚠️ Volg altijd het [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) backup plan voor je kostbare foto's en video's! +> [!NOTE] +> De belangrijkste documentatie, inclusief installatie handleidingen, zijn te vinden op https://immich.app/. ## Inhoud -- [Officiële documentatie](https://docs.immich.app) -- [Toekomstplannen](https://github.com/orgs/immich-app/projects/1) -- [Demo](#demo) -- [Functies](#functies) +- [Officiële documentatie](https://docs.immich.app/) - [Introductie](https://docs.immich.app/overview/introduction) - [Installatie](https://docs.immich.app/install/requirements) +- [Toekomstplannen](https://immich.app/roadmap) +- [Demo](#demo) +- [Functies](#functies) +- [Vertalingen](https://docs.immich.app/developer/translations) - [Richtlijnen voor bijdragen](https://docs.immich.app/overview/support-the-project) -## Documentatie - -De belangrijkste documentatie, inclusief installatie handleidingen, zijn te vinden op https://immich.app/. - ## Demo Je kunt de demo [hier](https://demo.immich.app/) bekijken. Voor de mobiele app kun je gebruik maken van `https://demo.immich.app` voor de `Server Endpoint URL`. diff --git a/readme_i18n/README_pt_BR.md b/readme_i18n/README_pt_BR.md index d6f51cd779..1e1861a797 100644 --- a/readme_i18n/README_pt_BR.md +++ b/readme_i18n/README_pt_BR.md @@ -40,16 +40,9 @@

-## Avisos - -- ⚠️ Este projeto está sob **desenvolvimento constante**. -- ⚠️ Podem ocorrer bugs e _breaking changes_ (alterações que quebram a - compatibilidade com versões anteriores). -- ⚠️ **Não use esta solução como a única forma de fazer backup das suas fotos e - vídeos.** -- ⚠️ Sempre siga o plano - [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) de backup - para as suas mídias preciosas! +> [!WARNING] +> ⚠️ Sempre siga o plano [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) de backup para as suas mídias preciosas! +> > [!NOTE] > Você pode encontrar a documentação principal, incluindo guias de instalação, em https://immich.app/. diff --git a/readme_i18n/README_ru_RU.md b/readme_i18n/README_ru_RU.md index 9c60e5f772..765e876f37 100644 --- a/readme_i18n/README_ru_RU.md +++ b/readme_i18n/README_ru_RU.md @@ -39,13 +39,9 @@ ภาษาไทย

-## Предупреждение - -- ⚠️ Этот проект находится **в очень активной** разработке. -- ⚠️ Ожидайте недоработки и глобальные изменения. -- ⚠️ **Не используйте это приложение как единственное хранилище своих фото и видео.** -- ⚠️ Всегда следуйте [плану резервного копирования «3-2-1»](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/ "Стратегии резервного копирования: Почему стратегия резервного копирования «3-2-1» — лучшая") для ваших драгоценных фотографий и видео! - +> [!WARNING] +> ⚠️ Всегда следуйте [плану резервного копирования «3-2-1»](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) для ваших драгоценных фотографий и видео! +> > [!NOTE] > Инструкции по установке и документация по ссылке https://immich.app/ @@ -55,7 +51,7 @@ - [Официальная документация](https://docs.immich.app) - [Введение](https://docs.immich.app/overview/introduction) - [Установка](https://docs.immich.app/install/requirements) -- [План разработки](https://github.com/orgs/immich-app/projects/1) +- [План разработки](https://immich.app/roadmap) - [Демо](#demo) - [Возможности](#features) - [Перевод](https://docs.immich.app/developer/translations) diff --git a/readme_i18n/README_sv_SE.md b/readme_i18n/README_sv_SE.md index a421c23c2e..b3a3cc804f 100644 --- a/readme_i18n/README_sv_SE.md +++ b/readme_i18n/README_sv_SE.md @@ -38,27 +38,24 @@ ภาษาไทย

-## Ansvarsfriskrivning +> [!WARNING] +> ⚠️ Tillämpa alltid [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/)-strategin för säkerhetskopiering av dina foton och videor! +> -- ⚠️ Projektet är under **mycket aktiv** utveckling. -- ⚠️ Förvänta dig buggar och brytande förändringar. -- ⚠️ **Använd inte appen som enda lagringssätt för dina foton och videor.** -- ⚠️ Tillämpa alltid [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/)-strategin för säkerhetskopiering av dina foton och videor! +> [!NOTE] +> Dokumentation och installationsguider hittas på https://immich.app/. ## Innehåll -- [Officiell Dokumentation](https://docs.immich.app) -- [Roadmap](https://github.com/orgs/immich-app/projects/1) -- [Demo](#demo) -- [Funktioner](#features) +- [Officiell Dokumentation](https://docs.immich.app/) - [Introduktion](https://docs.immich.app/overview/introduction) - [Installation](https://docs.immich.app/install/requirements) +- [Roadmap](https://immich.app/roadmap) +- [Demo](#demo) +- [Funktioner](#funktioner) +- [Översättningar](https://docs.immich.app/developer/translations) - [Riktlinjer för Bidrag](https://docs.immich.app/overview/support-the-project) -## Dokumentation - -Dokumentation och installationsguider hittas på https://imiich.app/. - ## Demo Ett webb-demo finns att testa på https://demo.immich.app. Använd `https://demo.immich.app` i mobilappen som `Server Endpoint URL` diff --git a/readme_i18n/README_tr_TR.md b/readme_i18n/README_tr_TR.md index 46aef49745..5ca61df023 100644 --- a/readme_i18n/README_tr_TR.md +++ b/readme_i18n/README_tr_TR.md @@ -37,26 +37,24 @@ ภาษาไทย

-## Feragatname +> [!WARNING] +> ⚠️ Always follow [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) backup plan for your precious photos and videos! +> -- ⚠️ Proje **çok aktif** bir şekilde geliştirilmektedir. -- ⚠️ Hatalar ve uygulama yapısını bozan değişiklikler olabilir. -- ⚠️ **Uygulamayı, fotoğraflarınızı ve videolarınızı saklamanın tek yöntemi olarak kullanmayın!** +> [!NOTE] +> Kurulum dahil olmak üzere resmi belgeleri https://immich.app/ adresinde bulabilirsiniz. -## Content +## Bağlantılar -- [Resmi Belgeler](https://docs.immich.app) -- [Yol Haritası](https://github.com/orgs/immich-app/projects/1) -- [Demo](#demo) -- [Özellikler](#özellikler) +- [Resmi Belgeler](https://docs.immich.app/) - [Giriş](https://docs.immich.app/overview/introduction) - [Kurulum](https://docs.immich.app/install/requirements) +- [Yol Haritası](https://immich.app/roadmap) +- [Demo](#demo) +- [Özellikler](#özellikler) +- [Çeviriler](https://docs.immich.app/developer/translations) - [Katkı Sağlama Rehberi](https://docs.immich.app/overview/support-the-project) -## Belgeler - -Kurulum dahil olmak üzere resmi belgeleri https://immich.app/ adresinde bulabilirsiniz. - ## Demo Web demo adresi: https://demo.immich.app. Mobil uygulama için `Server Endpoint URL` olarak `https://demo.immich.app` adresini kullanabilirsiniz. diff --git a/readme_i18n/README_uk_UA.md b/readme_i18n/README_uk_UA.md index 297054ee42..6b5849b01c 100644 --- a/readme_i18n/README_uk_UA.md +++ b/readme_i18n/README_uk_UA.md @@ -39,12 +39,9 @@ ภาษาไทย

-## Застереження - -- ⚠️ Цей проєкт перебуває **в дуже активній** розробці. -- ⚠️ Очікуйте безліч помилок і глобальних змін. -- ⚠️ **Не використовуйте цей застосунок як єдине сховище своїх фото та відео.** -- ⚠️ Завжди дотримуйтесь [плану резервного копіювання 3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) для ваших дорогоцінних фотографій та відео! +> [!WARNING] +> ⚠️ Завжди дотримуйтесь [плану резервного копіювання 3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) для ваших дорогоцінних фотографій та відео! +> > [!NOTE] > Основну документацію, зокрема посібники зі встановлення, можна знайти за адресою https://immich.app/. diff --git a/readme_i18n/README_vi_VN.md b/readme_i18n/README_vi_VN.md index b6b22ff610..54e8a9bb90 100644 --- a/readme_i18n/README_vi_VN.md +++ b/readme_i18n/README_vi_VN.md @@ -41,12 +41,9 @@

-## Tuyên bố miễn trừ trách nhiệm - -- ⚠️ Dự án đang được phát triển **rất tích cực**. -- ⚠️ Dự kiến ​​sẽ có lỗi và thay đổi đột ngột. -- ⚠️ **Không sử dụng ứng dụng như là cách duy nhất để lưu trữ ảnh và video của bạn.** -- ⚠️ Luôn tuân thủ kế hoạch sao lưu [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) cho những bức ảnh và video quý giá của bạn! +> [!WARNING] +> ⚠️ Luôn tuân thủ kế hoạch sao lưu [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) cho những bức ảnh và video quý giá của bạn! +> > [!NOTE] > Bạn có thể tìm thấy tài liệu chính, bao gồm hướng dẫn cài đặt, tại https://immich.app/. diff --git a/readme_i18n/README_zh_CN.md b/readme_i18n/README_zh_CN.md index b48e69f94d..6bd82b13e2 100644 --- a/readme_i18n/README_zh_CN.md +++ b/readme_i18n/README_zh_CN.md @@ -43,12 +43,9 @@

-## 免责声明 - -- ⚠️ 本项目正在 **非常活跃** 地开发中。 -- ⚠️ 可能存在 bug 或者随时有重大变更。 -- ⚠️ **不要把本软件作为您存储照片或视频的唯一方式。** -- ⚠️ 为了您宝贵的照片与视频,请始终遵守 [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) 备份方案! +> [!WARNING] +> ⚠️ 为了您宝贵的照片与视频,请始终遵守 [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) 备份方案! +> > [!NOTE] > 完整的项目文档以及安装教程请参见:。 diff --git a/server/Dockerfile b/server/Dockerfile index d35d029958..6078a97504 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -13,12 +13,13 @@ FROM builder AS server WORKDIR /usr/src/app COPY ./server ./server/ +COPY ./packages/plugin-sdk ./packages/plugin-sdk/ RUN --mount=type=cache,id=pnpm-server,target=/buildcache/pnpm-store \ --mount=type=bind,source=package.json,target=package.json \ --mount=type=bind,source=.pnpmfile.cjs,target=.pnpmfile.cjs \ --mount=type=bind,source=pnpm-lock.yaml,target=pnpm-lock.yaml \ --mount=type=bind,source=pnpm-workspace.yaml,target=pnpm-workspace.yaml \ - SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm --filter immich --frozen-lockfile build && \ + SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm --filter immich --filter @immich/plugin-sdk --frozen-lockfile build && \ SHARP_FORCE_GLOBAL_LIBVIPS=true pnpm --filter immich --frozen-lockfile --prod --no-optional deploy /output/server-pruned FROM builder AS web @@ -54,24 +55,28 @@ FROM builder AS plugins ARG TARGETPLATFORM -COPY --from=ghcr.io/jdx/mise:2026.3.12@sha256:0210678cbf58413806531a27adb2c7daf1c37238e56e8f7ea381d73521571775 /usr/local/bin/mise /usr/local/bin/mise +COPY --from=ghcr.io/jdx/mise:2026.5.11@sha256:2ba959e4827f845fe0c4cfb4814089e790dc513040ef74f9e14925f446412a51 /usr/local/bin/mise /usr/local/bin/mise -WORKDIR /usr/src/app -COPY ./packages/plugins/mise.toml ./packages/plugins/ -ENV MISE_TRUSTED_CONFIG_PATHS=/usr/src/app/packages/plugins/mise.toml +WORKDIR /app +COPY ./mise.toml ./mise.toml +COPY ./packages/plugin-core/mise.toml ./packages/plugin-core/ +ENV MISE_TRUSTED_CONFIG_PATHS=/app/mise.toml ENV MISE_DATA_DIR=/buildcache/mise +ENV MISE_DISABLE_TOOLS=flutter RUN --mount=type=cache,id=mise-tools-${TARGETPLATFORM},target=/buildcache/mise \ - mise install --cd packages/plugins + mise install + +COPY ./packages/plugin-core ./packages/plugin-core/ +COPY ./packages/plugin-sdk ./packages/plugin-sdk/ -COPY ./packages/plugins ./packages/plugins/ # Build plugins -RUN --mount=type=cache,id=pnpm-plugins,target=/buildcache/pnpm-store \ +RUN --mount=type=cache,id=pnpm-packages,target=/buildcache/pnpm-store \ --mount=type=bind,source=package.json,target=package.json \ --mount=type=bind,source=.pnpmfile.cjs,target=.pnpmfile.cjs \ --mount=type=bind,source=pnpm-lock.yaml,target=pnpm-lock.yaml \ --mount=type=bind,source=pnpm-workspace.yaml,target=pnpm-workspace.yaml \ --mount=type=cache,id=mise-tools-${TARGETPLATFORM},target=/buildcache/mise \ - cd packages/plugins && mise run build + mise //:plugins FROM ghcr.io/immich-app/base-server-prod:202605051129@sha256:50f7ffe4ed31e360c90c4905bd5f6658f2a121297544e3fe9368e338b3f76bcd @@ -83,8 +88,8 @@ ENV NODE_ENV=production \ COPY --from=server /output/server-pruned ./server COPY --from=web /usr/src/app/web/build /build/www COPY --from=cli /output/cli-pruned ./cli -COPY --from=plugins /usr/src/app/packages/plugins/dist /build/corePlugin/dist -COPY --from=plugins /usr/src/app/packages/plugins/manifest.json /build/corePlugin/manifest.json +COPY --from=plugins /app/packages/plugin-core/dist /build/plugins/immich-plugin-core/dist +COPY --from=plugins /app/packages/plugin-core/manifest.json /build/plugins/immich-plugin-core/manifest.json RUN ln -s ../../cli/bin/immich server/bin/immich COPY LICENSE /licenses/LICENSE.txt COPY LICENSE /LICENSE diff --git a/server/mise.toml b/server/mise.toml index b8236c60c6..f7e4f92a26 100644 --- a/server/mise.toml +++ b/server/mise.toml @@ -58,6 +58,7 @@ run = "email dev -p 3050 --dir src/emails" [tasks.ci-unit] run = [ { task = ":install" }, + { task = "//:plugins" }, { task = ":format" }, { task = ":lint" }, { task = ":check" }, @@ -67,6 +68,7 @@ run = [ [tasks.ci-medium] run = [ { task = ":install" }, + { task = "//packages/plugin-core:build" }, { task = ":test-medium --run" }, ] diff --git a/server/package.json b/server/package.json index 904cb3c6c8..119a1ea603 100644 --- a/server/package.json +++ b/server/package.json @@ -37,6 +37,7 @@ }, "dependencies": { "@extism/extism": "2.0.0-rc13", + "@immich/plugin-sdk": "workspace:*", "@immich/sql-tools": "^0.5.1", "@nestjs/bullmq": "^11.0.1", "@nestjs/common": "^11.0.4", @@ -48,14 +49,14 @@ "@nestjs/websockets": "^11.0.4", "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^2.0.0", - "@opentelemetry/exporter-prometheus": "^0.217.0", - "@opentelemetry/instrumentation-http": "^0.215.0", - "@opentelemetry/instrumentation-ioredis": "^0.63.0", - "@opentelemetry/instrumentation-nestjs-core": "^0.61.0", - "@opentelemetry/instrumentation-pg": "^0.67.0", + "@opentelemetry/exporter-prometheus": "^0.218.0", + "@opentelemetry/instrumentation-http": "^0.218.0", + "@opentelemetry/instrumentation-ioredis": "^0.66.0", + "@opentelemetry/instrumentation-nestjs-core": "^0.64.0", + "@opentelemetry/instrumentation-pg": "^0.70.0", "@opentelemetry/resources": "^2.0.1", "@opentelemetry/sdk-metrics": "^2.0.1", - "@opentelemetry/sdk-node": "^0.217.0", + "@opentelemetry/sdk-node": "^0.218.0", "@opentelemetry/semantic-conventions": "^1.34.0", "@react-email/components": "^1.0.0", "@react-email/render": "^2.0.0", @@ -74,6 +75,7 @@ "express": "^5.1.0", "fast-glob": "^3.3.2", "fluent-ffmpeg": "^2.1.2", + "generic-pool": "^3.9.0", "geo-tz": "^8.0.0", "handlebars": "^4.7.8", "helmet": "^8.1.0", @@ -114,7 +116,7 @@ "ua-parser-js": "^2.0.0", "uuid": "^14.0.0", "validator": "^13.12.0", - "zod": "^4.3.6" + "zod": "4.3.6" }, "devDependencies": { "@eslint/js": "^10.0.0", @@ -136,7 +138,7 @@ "@types/luxon": "^3.6.2", "@types/mock-fs": "^4.13.1", "@types/multer": "^2.0.0", - "@types/node": "^24.12.2", + "@types/node": "^24.12.4", "@types/nodemailer": "^8.0.0", "@types/picomatch": "^4.0.0", "@types/pngjs": "^6.0.5", diff --git a/server/src/controllers/plugin.controller.spec.ts b/server/src/controllers/plugin.controller.spec.ts new file mode 100644 index 0000000000..881a7dd953 --- /dev/null +++ b/server/src/controllers/plugin.controller.spec.ts @@ -0,0 +1,56 @@ +import { PluginController } from 'src/controllers/plugin.controller'; +import { LoggingRepository } from 'src/repositories/logging.repository'; +import { PluginService } from 'src/services/plugin.service'; +import request from 'supertest'; +import { errorDto } from 'test/medium/responses'; +import { factory } from 'test/small.factory'; +import { automock, ControllerContext, controllerSetup, mockBaseService } from 'test/utils'; + +describe(PluginController.name, () => { + let ctx: ControllerContext; + const service = mockBaseService(PluginService); + + beforeAll(async () => { + ctx = await controllerSetup(PluginController, [ + { provide: PluginService, useValue: service }, + { provide: LoggingRepository, useValue: automock(LoggingRepository, { strict: false }) }, + ]); + return () => ctx.close(); + }); + + beforeEach(() => { + service.resetAllMocks(); + ctx.reset(); + }); + + describe('GET /plugins', () => { + it('should be an authenticated route', async () => { + await request(ctx.getHttpServer()).get('/plugins'); + expect(ctx.authenticate).toHaveBeenCalled(); + }); + + it(`should require id to be a uuid`, async () => { + const { status, body } = await request(ctx.getHttpServer()) + .get(`/plugins`) + .query({ id: 'invalid' }) + .set('Authorization', `Bearer token`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.validationError([{ path: ['id'], message: 'Invalid UUID' }])); + }); + }); + + describe('GET /plugins/:id', () => { + it('should be an authenticated route', async () => { + await request(ctx.getHttpServer()).get(`/plugins/${factory.uuid()}`); + expect(ctx.authenticate).toHaveBeenCalled(); + }); + + it(`should require id to be a uuid`, async () => { + const { status, body } = await request(ctx.getHttpServer()) + .get(`/plugins/invalid`) + .set('Authorization', `Bearer token`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.validationError([{ path: ['id'], message: 'Invalid UUID' }])); + }); + }); +}); diff --git a/server/src/controllers/plugin.controller.ts b/server/src/controllers/plugin.controller.ts index 52c833e93d..fb1bda287e 100644 --- a/server/src/controllers/plugin.controller.ts +++ b/server/src/controllers/plugin.controller.ts @@ -1,7 +1,12 @@ -import { Controller, Get, Param } from '@nestjs/common'; +import { Controller, Get, Param, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { PluginResponseDto, PluginTriggerResponseDto } from 'src/dtos/plugin.dto'; +import { + PluginMethodResponseDto, + PluginMethodSearchDto, + PluginResponseDto, + PluginSearchDto, +} from 'src/dtos/plugin.dto'; import { Permission } from 'src/enum'; import { Authenticated } from 'src/middleware/auth.guard'; import { PluginService } from 'src/services/plugin.service'; @@ -12,26 +17,26 @@ import { UUIDParamDto } from 'src/validation'; export class PluginController { constructor(private service: PluginService) {} - @Get('triggers') - @Authenticated({ permission: Permission.PluginRead }) - @Endpoint({ - summary: 'List all plugin triggers', - description: 'Retrieve a list of all available plugin triggers.', - history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), - }) - getPluginTriggers(): PluginTriggerResponseDto[] { - return this.service.getTriggers(); - } - @Get() @Authenticated({ permission: Permission.PluginRead }) @Endpoint({ summary: 'List all plugins', description: 'Retrieve a list of plugins available to the authenticated user.', - history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), + history: HistoryBuilder.v3(), }) - getPlugins(): Promise { - return this.service.getAll(); + searchPlugins(@Query() dto: PluginSearchDto): Promise { + return this.service.search(dto); + } + + @Get('methods') + @Authenticated({ permission: Permission.PluginRead }) + @Endpoint({ + summary: 'Retrieve plugin methods', + description: 'Retrieve a list of plugin methods', + history: HistoryBuilder.v3(), + }) + searchPluginMethods(@Query() dto: PluginMethodSearchDto): Promise { + return this.service.searchMethods(dto); } @Get(':id') @@ -39,7 +44,7 @@ export class PluginController { @Endpoint({ summary: 'Retrieve a plugin', description: 'Retrieve information about a specific plugin by its ID.', - history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), + history: HistoryBuilder.v3(), }) getPlugin(@Param() { id }: UUIDParamDto): Promise { return this.service.get(id); diff --git a/server/src/controllers/workflow.controller.spec.ts b/server/src/controllers/workflow.controller.spec.ts new file mode 100644 index 0000000000..7bc164e285 --- /dev/null +++ b/server/src/controllers/workflow.controller.spec.ts @@ -0,0 +1,113 @@ +import { WorkflowController } from 'src/controllers/workflow.controller'; +import { WorkflowTrigger } from 'src/enum'; +import { LoggingRepository } from 'src/repositories/logging.repository'; +import { WorkflowService } from 'src/services/workflow.service'; +import request from 'supertest'; +import { errorDto } from 'test/medium/responses'; +import { factory } from 'test/small.factory'; +import { automock, ControllerContext, controllerSetup, mockBaseService } from 'test/utils'; + +describe(WorkflowController.name, () => { + let ctx: ControllerContext; + const service = mockBaseService(WorkflowService); + + beforeAll(async () => { + ctx = await controllerSetup(WorkflowController, [ + { provide: WorkflowService, useValue: service }, + { provide: LoggingRepository, useValue: automock(LoggingRepository, { strict: false }) }, + ]); + return () => ctx.close(); + }); + + beforeEach(() => { + service.resetAllMocks(); + ctx.reset(); + }); + + describe('POST /workflows', () => { + it('should be an authenticated route', async () => { + await request(ctx.getHttpServer()).post('/workflows').send({}); + expect(ctx.authenticate).toHaveBeenCalled(); + }); + + it(`should require a valid trigger`, async () => { + const { status, body } = await request(ctx.getHttpServer()) + .post(`/workflows`) + .send({ trigger: 'invalid' }) + .set('Authorization', `Bearer token`); + expect(status).toBe(400); + expect(body).toEqual( + errorDto.validationError([ + { path: ['trigger'], message: expect.stringContaining('Invalid option: expected one of') }, + ]), + ); + }); + + it(`should require a valid enabled value`, async () => { + const { status, body } = await request(ctx.getHttpServer()) + .post(`/workflows`) + .send({ enabled: 'invalid' }) + .set('Authorization', `Bearer token`); + expect(status).toBe(400); + expect(body).toEqual( + errorDto.validationError([{ path: ['enabled'], message: 'Invalid input: expected boolean, received string' }]), + ); + }); + + it(`should not require a name`, async () => { + const { status } = await request(ctx.getHttpServer()) + .post(`/workflows`) + .send({ trigger: WorkflowTrigger.AssetCreate }) + .set('Authorization', `Bearer token`); + expect(status).toBe(201); + expect(service.create).toHaveBeenCalled(); + }); + }); + + describe('GET /workflows', () => { + it('should be an authenticated route', async () => { + await request(ctx.getHttpServer()).get('/workflows'); + expect(ctx.authenticate).toHaveBeenCalled(); + }); + + it(`should require id to be a uuid`, async () => { + const { status, body } = await request(ctx.getHttpServer()) + .get(`/workflows`) + .query({ id: 'invalid' }) + .set('Authorization', `Bearer token`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.validationError([{ path: ['id'], message: 'Invalid UUID' }])); + }); + }); + + describe('GET /workflows/:id', () => { + it('should be an authenticated route', async () => { + await request(ctx.getHttpServer()).get(`/workflows/${factory.uuid()}`); + expect(ctx.authenticate).toHaveBeenCalled(); + }); + + it(`should require id to be a uuid`, async () => { + const { status, body } = await request(ctx.getHttpServer()) + .get(`/workflows/invalid`) + .set('Authorization', `Bearer token`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.validationError([{ path: ['id'], message: 'Invalid UUID' }])); + }); + }); + + describe('PUT /workflows/:id', () => { + it('should be an authenticated route', async () => { + await request(ctx.getHttpServer()).put(`/workflows/${factory.uuid()}`).send({}); + expect(ctx.authenticate).toHaveBeenCalled(); + }); + + it(`should require id to be a uuid`, async () => { + const { status, body } = await request(ctx.getHttpServer()) + .put(`/workflows/invalid`) + .set('Authorization', `Bearer token`) + .send({}); + expect(status).toBe(400); + expect(body).toEqual(errorDto.validationError([{ path: ['id'], message: 'Invalid UUID' }])); + }); + }); +}); diff --git a/server/src/controllers/workflow.controller.ts b/server/src/controllers/workflow.controller.ts index e07b6443f4..59fa2eee27 100644 --- a/server/src/controllers/workflow.controller.ts +++ b/server/src/controllers/workflow.controller.ts @@ -1,8 +1,15 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put } from '@nestjs/common'; +import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { Endpoint, HistoryBuilder } from 'src/decorators'; import { AuthDto } from 'src/dtos/auth.dto'; -import { WorkflowCreateDto, WorkflowResponseDto, WorkflowUpdateDto } from 'src/dtos/workflow.dto'; +import { + WorkflowCreateDto, + WorkflowResponseDto, + WorkflowSearchDto, + WorkflowShareResponseDto, + WorkflowTriggerResponseDto, + WorkflowUpdateDto, +} from 'src/dtos/workflow.dto'; import { Permission } from 'src/enum'; import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { WorkflowService } from 'src/services/workflow.service'; @@ -18,7 +25,7 @@ export class WorkflowController { @Endpoint({ summary: 'Create a workflow', description: 'Create a new workflow, the workflow can also be created with empty filters and actions.', - history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), + history: HistoryBuilder.v3(), }) createWorkflow(@Auth() auth: AuthDto, @Body() dto: WorkflowCreateDto): Promise { return this.service.create(auth, dto); @@ -29,10 +36,21 @@ export class WorkflowController { @Endpoint({ summary: 'List all workflows', description: 'Retrieve a list of workflows available to the authenticated user.', - history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), + history: HistoryBuilder.v3(), }) - getWorkflows(@Auth() auth: AuthDto): Promise { - return this.service.getAll(auth); + searchWorkflows(@Auth() auth: AuthDto, @Query() dto: WorkflowSearchDto): Promise { + return this.service.search(auth, dto); + } + + @Get('triggers') + @Authenticated({ permission: false }) + @Endpoint({ + summary: 'List all workflow triggers', + description: 'Retrieve a list of all available workflow triggers.', + history: HistoryBuilder.v3(), + }) + getWorkflowTriggers(): WorkflowTriggerResponseDto[] { + return this.service.getTriggers(); } @Get(':id') @@ -40,19 +58,30 @@ export class WorkflowController { @Endpoint({ summary: 'Retrieve a workflow', description: 'Retrieve information about a specific workflow by its ID.', - history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), + history: HistoryBuilder.v3(), }) getWorkflow(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.get(auth, id); } + @Get(':id/share') + @Authenticated({ permission: Permission.WorkflowRead }) + @Endpoint({ + summary: 'Retrieve a workflow', + description: 'Retrieve a workflow details without ids, default values, etc.', + history: HistoryBuilder.v3(), + }) + getWorkflowForShare(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { + return this.service.share(auth, id); + } + @Put(':id') @Authenticated({ permission: Permission.WorkflowUpdate }) @Endpoint({ summary: 'Update a workflow', description: 'Update the information of a specific workflow by its ID. This endpoint can be used to update the workflow name, description, trigger type, filters and actions order, etc.', - history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), + history: HistoryBuilder.v3(), }) updateWorkflow( @Auth() auth: AuthDto, @@ -68,7 +97,7 @@ export class WorkflowController { @Endpoint({ summary: 'Delete a workflow', description: 'Delete a workflow by its ID.', - history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), + history: HistoryBuilder.v3(), }) deleteWorkflow(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.delete(auth, id); diff --git a/server/src/database.ts b/server/src/database.ts index 934854e5c4..08f080e0ae 100644 --- a/server/src/database.ts +++ b/server/src/database.ts @@ -8,8 +8,6 @@ import { ChecksumAlgorithm, MemoryType, Permission, - PluginContext, - PluginTriggerType, SharedLinkType, SourceType, UserAvatarColor, @@ -18,10 +16,8 @@ import { import { AlbumTable } from 'src/schema/tables/album.table'; import { AssetExifTable } from 'src/schema/tables/asset-exif.table'; import { AssetTable } from 'src/schema/tables/asset.table'; -import { PluginActionTable, PluginFilterTable } from 'src/schema/tables/plugin.table'; -import { WorkflowActionTable, WorkflowFilterTable, WorkflowTable } from 'src/schema/tables/workflow.table'; +import { PluginTable } from 'src/schema/tables/plugin.table'; import { UserMetadataItem } from 'src/types'; -import type { ActionConfig, FilterConfig, JSONSchema } from 'src/types/plugin-schema.types'; export type AuthUser = { id: string; @@ -276,42 +272,7 @@ export type AssetFace = { isVisible: boolean; }; -export type PluginFilter = Selectable & { - methodName: string; - title: string; - description: string; - supportedContexts: PluginContext[]; - schema: JSONSchema | null; -}; - -export type PluginAction = Selectable & { - methodName: string; - title: string; - description: string; - supportedContexts: PluginContext[]; - schema: JSONSchema | null; -}; - -export type Workflow = Selectable & { - triggerType: PluginTriggerType; - name: string | null; - description: string; - enabled: boolean; -}; - -export type WorkflowFilter = Selectable & { - workflowId: string; - pluginFilterId: string; - filterConfig: FilterConfig | null; - order: number; -}; - -export type WorkflowAction = Selectable & { - workflowId: string; - pluginActionId: string; - actionConfig: ActionConfig | null; - order: number; -}; +export type Plugin = Selectable; const userColumns = ['id', 'name', 'email', 'avatarColor', 'profileImagePath', 'profileChangedAt'] as const; const userWithPrefixColumns = [ @@ -343,6 +304,32 @@ export const columns = { 'asset.height', 'asset.isEdited', ], + workflowAssetV1: [ + 'asset.id', + 'asset.ownerId', + 'asset.stackId', + 'asset.livePhotoVideoId', + 'asset.libraryId', + 'asset.duplicateId', + 'asset.createdAt', + 'asset.updatedAt', + 'asset.deletedAt', + 'asset.fileCreatedAt', + 'asset.fileModifiedAt', + 'asset.localDateTime', + 'asset.type', + 'asset.status', + 'asset.visibility', + 'asset.duration', + 'asset.checksum', + 'asset.originalPath', + 'asset.originalFileName', + 'asset.isOffline', + 'asset.isFavorite', + 'asset.isExternal', + 'asset.isEdited', + 'asset.isFavorite', + ], assetFiles: ['asset_file.id', 'asset_file.path', 'asset_file.type', 'asset_file.isEdited'], assetFilesForThumbnail: [ 'asset_file.id', @@ -374,6 +361,15 @@ export const columns = { tag: ['tag.id', 'tag.value', 'tag.createdAt', 'tag.updatedAt', 'tag.color', 'tag.parentId'], apiKey: ['id', 'name', 'userId', 'createdAt', 'updatedAt', 'permissions'], notification: ['id', 'createdAt', 'level', 'type', 'title', 'description', 'data', 'readAt'], + pluginMethod: [ + 'plugin_method.name', + 'plugin_method.title', + 'plugin_method.description', + 'plugin_method.types', + 'plugin_method.schema', + 'plugin_method.hostFunctions', + 'plugin_method.uiHints', + ], syncAsset: [ 'asset.id', 'asset.ownerId', @@ -487,17 +483,6 @@ export const columns = { 'asset_exif.tags', 'asset_exif.timeZone', ], - plugin: [ - 'plugin.id as id', - 'plugin.name as name', - 'plugin.title as title', - 'plugin.description as description', - 'plugin.author as author', - 'plugin.version as version', - 'plugin.wasmPath as wasmPath', - 'plugin.createdAt as createdAt', - 'plugin.updatedAt as updatedAt', - ], } as const; export type LockableProperty = (typeof lockableProperties)[number]; diff --git a/server/src/decorators.ts b/server/src/decorators.ts index 75a3463ee7..c8cf1f9221 100644 --- a/server/src/decorators.ts +++ b/server/src/decorators.ts @@ -200,6 +200,10 @@ export class HistoryBuilder { private hasDeprecated = false; private items: HistoryEntry[] = []; + static v3() { + return new HistoryBuilder().added('v3.0.0'); + } + added(version: string, description?: string) { return this.push({ version, state: 'Added', description }); } diff --git a/server/src/dtos/activity.dto.ts b/server/src/dtos/activity.dto.ts index 7b8ba34c91..facd4ed256 100644 --- a/server/src/dtos/activity.dto.ts +++ b/server/src/dtos/activity.dto.ts @@ -36,18 +36,16 @@ const ActivityStatisticsResponseSchema = z }) .meta({ id: 'ActivityStatisticsResponseDto' }); -const ActivitySchema = z - .object({ - albumId: z.uuidv4().describe('Album ID'), - assetId: z.uuidv4().optional().describe('Asset ID (if activity is for an asset)'), - }) - .describe('Activity'); +const ActivitySchema = z.object({ + albumId: z.uuidv4().describe('Album ID'), + assetId: z.uuidv4().optional().describe('Asset ID (if activity is for an asset)'), +}); const ActivitySearchSchema = ActivitySchema.extend({ type: ReactionTypeSchema.optional(), level: ReactionLevelSchema.optional(), userId: z.uuidv4().optional().describe('Filter by user ID'), -}).describe('Activity search'); +}); const ActivityCreateSchema = ActivitySchema.extend({ type: ReactionTypeSchema, diff --git a/server/src/dtos/json-schema.dto.ts b/server/src/dtos/json-schema.dto.ts new file mode 100644 index 0000000000..65b92277d5 --- /dev/null +++ b/server/src/dtos/json-schema.dto.ts @@ -0,0 +1,32 @@ +import { createZodDto } from 'nestjs-zod'; +import z from 'zod'; + +export const JsonSchemaTypeSchema = z + .enum(['string', 'number', 'integer', 'boolean', 'object']) + .meta({ id: 'JsonSchemaType' }); + +const JsonSchemaPropertySchema = z + .object({ + type: JsonSchemaTypeSchema.optional().default('object').describe('Type'), + title: z.string().describe('Title'), + description: z.string().describe('Description'), + default: z.any().optional().describe('Default value'), + enum: z.array(z.string()).optional().describe('Valid choices for enum types'), + array: z.boolean().optional().describe('Type is an array type'), + required: z.array(z.string()).optional().describe('A list of required properties'), + uiHint: z.string().optional(), + get properties() { + return z.record(z.string(), JsonSchemaPropertySchema).optional(); + }, + }) + .meta({ id: 'JsonSchemaPropertyDto' }); + +export const JsonSchemaSchema = z + .object({ + ...JsonSchemaPropertySchema.shape, + title: z.string().optional().describe('Title'), + description: z.string().optional().describe('Description'), + }) + .meta({ id: 'JsonSchemaDto' }); + +export class JsonSchemaDto extends createZodDto(JsonSchemaSchema) {} diff --git a/server/src/dtos/plugin-manifest.dto.ts b/server/src/dtos/plugin-manifest.dto.ts index 30aa8c0a68..4d004a375d 100644 --- a/server/src/dtos/plugin-manifest.dto.ts +++ b/server/src/dtos/plugin-manifest.dto.ts @@ -1,39 +1,29 @@ import { createZodDto } from 'nestjs-zod'; -import { PluginContextSchema } from 'src/enum'; -import { JSONSchemaSchema } from 'src/types/plugin-schema.types'; +import { JsonSchemaSchema } from 'src/dtos/json-schema.dto'; +import { WorkflowTypeSchema } from 'src/enum'; import z from 'zod'; const pluginNameRegex = /^[a-z0-9-]+[a-z0-9]$/; const semverRegex = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/; -const PluginManifestWasmSchema = z - .object({ - path: z.string().describe('WASM file path'), - }) - .meta({ id: 'PluginManifestWasmDto' }); +export const PluginManifestMethodSchemaSchema = JsonSchemaSchema.nullable() + .optional() + .transform((value) => (value && Object.keys(value).length === 0 ? null : value)); -const PluginManifestFilterSchema = z +const PluginManifestMethodSchema = z .object({ - methodName: z.string().describe('Filter method name'), - title: z.string().describe('Filter title'), - description: z.string().describe('Filter description'), - supportedContexts: z.array(PluginContextSchema).min(1).describe('Supported contexts'), - schema: JSONSchemaSchema.optional(), + name: z.string().min(1).describe('Method name'), + title: z.string().min(1).describe('Method title'), + description: z.string().min(1).describe('Method description'), + types: z.array(WorkflowTypeSchema).min(1).describe('Workflow type'), + hostFunctions: z.boolean().optional().default(false).describe('Method uses host functions'), + schema: PluginManifestMethodSchemaSchema.describe('Schema'), + uiHints: z.array(z.string()).optional().describe('Ui hints, for example "filter"'), }) - .meta({ id: 'PluginManifestFilterDto' }); + .meta({ id: 'PluginManifestMethodDto' }); -const PluginManifestActionSchema = z - .object({ - methodName: z.string().describe('Action method name'), - title: z.string().describe('Action title'), - description: z.string().describe('Action description'), - supportedContexts: z.array(PluginContextSchema).min(1).describe('Supported contexts'), - schema: JSONSchemaSchema.optional(), - }) - .meta({ id: 'PluginManifestActionDto' }); - -export const PluginManifestSchema = z +const PluginManifestSchema = z .object({ name: z .string() @@ -44,12 +34,11 @@ export const PluginManifestSchema = z ) .describe('Plugin name (lowercase, numbers, hyphens only)'), version: z.string().regex(semverRegex).describe('Plugin version (semver)'), - title: z.string().describe('Plugin title'), - description: z.string().describe('Plugin description'), - author: z.string().describe('Plugin author'), - wasm: PluginManifestWasmSchema, - filters: z.array(PluginManifestFilterSchema).optional().describe('Plugin filters'), - actions: z.array(PluginManifestActionSchema).optional().describe('Plugin actions'), + title: z.string().min(1).describe('Plugin title'), + description: z.string().min(1).describe('Plugin description'), + wasmPath: z.string().min(1).describe('WASM file path'), + author: z.string().min(1).describe('Plugin author'), + methods: z.array(PluginManifestMethodSchema).optional().default([]).describe('Plugin methods'), }) .meta({ id: 'PluginManifestDto' }); diff --git a/server/src/dtos/plugin.dto.ts b/server/src/dtos/plugin.dto.ts index 2f928841cb..410ada4b53 100644 --- a/server/src/dtos/plugin.dto.ts +++ b/server/src/dtos/plugin.dto.ts @@ -1,39 +1,33 @@ import { createZodDto } from 'nestjs-zod'; -import { PluginAction, PluginFilter } from 'src/database'; -import { PluginContextSchema, PluginTriggerTypeSchema } from 'src/enum'; -import { JSONSchemaSchema } from 'src/types/plugin-schema.types'; +import { JsonSchemaDto } from 'src/dtos/json-schema.dto'; +import { WorkflowTriggerSchema, WorkflowType, WorkflowTypeSchema } from 'src/enum'; +import { asMethodString } from 'src/utils/workflow'; import z from 'zod'; -const PluginTriggerResponseSchema = z +const PluginSearchSchema = z .object({ - type: PluginTriggerTypeSchema, - contextType: PluginContextSchema, + id: z.uuidv4().optional().describe('Plugin ID'), + enabled: z.boolean().optional().describe('Whether the plugin is enabled'), + name: z.string().optional(), + version: z.string().optional(), + title: z.string().optional(), + description: z.string().optional(), }) - .meta({ id: 'PluginTriggerResponseDto' }); + .meta({ id: 'PluginSearchDto' }); -const PluginFilterResponseSchema = z +const PluginMethodResponseSchema = z .object({ - id: z.string().describe('Filter ID'), - pluginId: z.string().describe('Plugin ID'), - methodName: z.string().describe('Method name'), - title: z.string().describe('Filter title'), - description: z.string().describe('Filter description'), - supportedContexts: z.array(PluginContextSchema).describe('Supported contexts'), - schema: JSONSchemaSchema.nullable().describe('Filter schema'), + key: z.string().describe('Key'), + name: z.string().describe('Name'), + title: z.string().describe('Title'), + description: z.string().describe('Description'), + types: z.array(WorkflowTypeSchema).describe('Workflow types'), + uiHints: z.array(z.string()).describe('Ui hints'), + // TODO fix this + schema: z.object().optional(), + hostFunctions: z.boolean(), }) - .meta({ id: 'PluginFilterResponseDto' }); - -const PluginActionResponseSchema = z - .object({ - id: z.string().describe('Action ID'), - pluginId: z.string().describe('Plugin ID'), - methodName: z.string().describe('Method name'), - title: z.string().describe('Action title'), - description: z.string().describe('Action description'), - supportedContexts: z.array(PluginContextSchema).describe('Supported contexts'), - schema: JSONSchemaSchema.nullable().describe('Action schema'), - }) - .meta({ id: 'PluginActionResponseDto' }); + .meta({ id: 'PluginMethodResponseDto' }); const PluginResponseSchema = z .object({ @@ -45,29 +39,53 @@ const PluginResponseSchema = z version: z.string().describe('Plugin version'), createdAt: z.string().describe('Creation date'), updatedAt: z.string().describe('Last update date'), - filters: z.array(PluginFilterResponseSchema).describe('Plugin filters'), - actions: z.array(PluginActionResponseSchema).describe('Plugin actions'), + methods: z.array(PluginMethodResponseSchema).describe('Plugin methods'), }) .meta({ id: 'PluginResponseDto' }); -export class PluginTriggerResponseDto extends createZodDto(PluginTriggerResponseSchema) {} -export class PluginResponseDto extends createZodDto(PluginResponseSchema) {} +const PluginMethodSearchSchema = z + .object({ + id: z.uuidv4().optional().describe('Plugin method ID'), + enabled: z.boolean().optional().describe('Whether the plugin method is enabled'), + name: z.string().optional(), + title: z.string().optional(), + description: z.string().optional(), + type: WorkflowTypeSchema.optional().describe('Workflow types'), + trigger: WorkflowTriggerSchema.optional().describe('Workflow trigger'), + pluginName: z.string().optional().describe('Plugin name'), + pluginVersion: z.string().optional().describe('Plugin version'), + }) + .meta({ id: 'PluginMethodSearchDto' }); -type MapPlugin = { +export class PluginSearchDto extends createZodDto(PluginSearchSchema) {} +export class PluginResponseDto extends createZodDto(PluginResponseSchema) {} +export class PluginMethodSearchDto extends createZodDto(PluginMethodSearchSchema) {} +export class PluginMethodResponseDto extends createZodDto(PluginMethodResponseSchema) {} + +type Plugin = { id: string; name: string; title: string; description: string; author: string; version: string; - wasmPath: string; createdAt: Date; updatedAt: Date; - filters: PluginFilter[]; - actions: PluginAction[]; + methods: PluginMethod[]; }; -export function mapPlugin(plugin: MapPlugin): PluginResponseDto { +type PluginMethod = { + pluginName: string; + name: string; + title: string; + description: string; + types: WorkflowType[]; + schema: JsonSchemaDto | null; + hostFunctions: boolean; + uiHints: string[]; +}; + +export function mapPlugin(plugin: Plugin): PluginResponseDto { return { id: plugin.id, name: plugin.name, @@ -77,7 +95,19 @@ export function mapPlugin(plugin: MapPlugin): PluginResponseDto { version: plugin.version, createdAt: plugin.createdAt.toISOString(), updatedAt: plugin.updatedAt.toISOString(), - filters: plugin.filters, - actions: plugin.actions, + methods: plugin.methods.map((method) => mapMethod(method)), }; } + +export const mapMethod = (method: PluginMethod): PluginMethodResponseDto => { + return { + key: asMethodString({ pluginName: method.pluginName, methodName: method.name }), + name: method.name, + title: method.title, + hostFunctions: method.hostFunctions, + uiHints: method.uiHints, + description: method.description, + types: method.types, + schema: method.schema as any, + }; +}; diff --git a/server/src/dtos/workflow.dto.ts b/server/src/dtos/workflow.dto.ts index f94e4bed92..8a5960470d 100644 --- a/server/src/dtos/workflow.dto.ts +++ b/server/src/dtos/workflow.dto.ts @@ -1,101 +1,135 @@ +import type { WorkflowStepConfig } from '@immich/plugin-sdk'; import { createZodDto } from 'nestjs-zod'; -import type { WorkflowAction, WorkflowFilter } from 'src/database'; -import { PluginTriggerTypeSchema } from 'src/enum'; -import { ActionConfigSchema, FilterConfigSchema } from 'src/types/plugin-schema.types'; +import { WorkflowTrigger, WorkflowTriggerSchema, WorkflowTypeSchema } from 'src/enum'; import z from 'zod'; -const WorkflowFilterItemSchema = z +const WorkflowTriggerResponseSchema = z .object({ - pluginFilterId: z.uuidv4().describe('Plugin filter ID'), - filterConfig: FilterConfigSchema.optional(), + trigger: WorkflowTriggerSchema.describe('Trigger type'), + types: z.array(WorkflowTypeSchema).describe('Workflow types'), }) - .meta({ id: 'WorkflowFilterItemDto' }); + .meta({ id: 'WorkflowTriggerResponseDto' }); -const WorkflowActionItemSchema = z +const WorkflowSearchSchema = z .object({ - pluginActionId: z.uuidv4().describe('Plugin action ID'), - actionConfig: ActionConfigSchema.optional(), + id: z.uuidv4().optional().describe('Workflow ID'), + trigger: WorkflowTriggerSchema.optional().describe('Workflow trigger type'), + name: z.string().optional().describe('Workflow name'), + description: z.string().optional().describe('Workflow description'), + enabled: z.boolean().optional().describe('Workflow enabled'), }) - .meta({ id: 'WorkflowActionItemDto' }); + .meta({ id: 'WorkflowSearchDto' }); + +const WorkflowStepSchema = z + .object({ + method: z.string().describe('Step plugin method'), + config: z.record(z.string(), z.unknown()).nullable().describe('Step configuration'), + enabled: z.boolean().optional().describe('Step is enabled'), + }) + .meta({ id: 'WorkflowStepDto' }); + +const WorkflowShareStepSchema = z + .object({ + method: z.string().describe('Step plugin method'), + config: z.record(z.string(), z.unknown()).nullable().describe('Step configuration'), + enabled: z.boolean().optional().describe('Step is enabled'), + }) + .meta({ id: 'WorkflowShareStepDto' }); const WorkflowCreateSchema = z .object({ - triggerType: PluginTriggerTypeSchema, - name: z.string().describe('Workflow name'), - description: z.string().optional().describe('Workflow description'), + trigger: WorkflowTriggerSchema.describe('Workflow trigger type'), + name: z.string().nullable().optional().describe('Workflow name'), + description: z.string().nullable().optional().describe('Workflow description'), enabled: z.boolean().optional().describe('Workflow enabled'), - filters: z.array(WorkflowFilterItemSchema).describe('Workflow filters'), - actions: z.array(WorkflowActionItemSchema).describe('Workflow actions'), + steps: z.array(WorkflowStepSchema).optional(), }) .meta({ id: 'WorkflowCreateDto' }); const WorkflowUpdateSchema = z .object({ - triggerType: PluginTriggerTypeSchema.optional(), - name: z.string().optional().describe('Workflow name'), - description: z.string().optional().describe('Workflow description'), + trigger: WorkflowTriggerSchema.optional().describe('Workflow trigger type'), + name: z.string().nullable().optional().describe('Workflow name'), + description: z.string().nullable().optional().describe('Workflow description'), enabled: z.boolean().optional().describe('Workflow enabled'), - filters: z.array(WorkflowFilterItemSchema).optional().describe('Workflow filters'), - actions: z.array(WorkflowActionItemSchema).optional().describe('Workflow actions'), + steps: z.array(WorkflowStepSchema).optional(), }) .meta({ id: 'WorkflowUpdateDto' }); -const WorkflowFilterResponseSchema = z - .object({ - id: z.string().describe('Filter ID'), - workflowId: z.string().describe('Workflow ID'), - pluginFilterId: z.string().describe('Plugin filter ID'), - filterConfig: FilterConfigSchema.nullable(), - order: z.int().describe('Filter order'), - }) - .meta({ id: 'WorkflowFilterResponseDto' }); - -const WorkflowActionResponseSchema = z - .object({ - id: z.string().describe('Action ID'), - workflowId: z.string().describe('Workflow ID'), - pluginActionId: z.string().describe('Plugin action ID'), - actionConfig: ActionConfigSchema.nullable(), - order: z.int().describe('Action order'), - }) - .meta({ id: 'WorkflowActionResponseDto' }); - const WorkflowResponseSchema = z .object({ id: z.string().describe('Workflow ID'), - ownerId: z.string().describe('Owner user ID'), - triggerType: PluginTriggerTypeSchema, + trigger: WorkflowTriggerSchema.describe('Workflow trigger type'), name: z.string().nullable().describe('Workflow name'), - description: z.string().describe('Workflow description'), + description: z.string().nullable().describe('Workflow description'), createdAt: z.string().describe('Creation date'), + updatedAt: z.string().describe('Update date'), enabled: z.boolean().describe('Workflow enabled'), - filters: z.array(WorkflowFilterResponseSchema).describe('Workflow filters'), - actions: z.array(WorkflowActionResponseSchema).describe('Workflow actions'), + steps: z.array(WorkflowStepSchema).describe('Workflow steps'), }) .meta({ id: 'WorkflowResponseDto' }); +const WorkflowShareResponseSchema = z + .object({ + trigger: WorkflowTriggerSchema.describe('Workflow trigger type'), + name: z.string().nullable().describe('Workflow name'), + description: z.string().nullable().describe('Workflow description'), + steps: z.array(WorkflowShareStepSchema).describe('Workflow steps'), + }) + .meta({ id: 'WorkflowShareResponseDto' }); + +export class WorkflowTriggerResponseDto extends createZodDto(WorkflowTriggerResponseSchema) {} +export class WorkflowSearchDto extends createZodDto(WorkflowSearchSchema) {} export class WorkflowCreateDto extends createZodDto(WorkflowCreateSchema) {} export class WorkflowUpdateDto extends createZodDto(WorkflowUpdateSchema) {} export class WorkflowResponseDto extends createZodDto(WorkflowResponseSchema) {} -class WorkflowFilterResponseDto extends createZodDto(WorkflowFilterResponseSchema) {} -class WorkflowActionResponseDto extends createZodDto(WorkflowActionResponseSchema) {} +export class WorkflowShareResponseDto extends createZodDto(WorkflowShareResponseSchema) {} -export function mapWorkflowFilter(filter: WorkflowFilter): WorkflowFilterResponseDto { - return { - id: filter.id, - workflowId: filter.workflowId, - pluginFilterId: filter.pluginFilterId, - filterConfig: filter.filterConfig, - order: filter.order, - }; -} +type Workflow = { + id: string; + createdAt: Date; + updatedAt: Date; + trigger: WorkflowTrigger; + name: string | null; + description: string | null; + enabled: boolean; +}; -export function mapWorkflowAction(action: WorkflowAction): WorkflowActionResponseDto { +type WorkflowStep = { + enabled: boolean; + methodName: string; + config: WorkflowStepConfig | null; + pluginName: string; +}; + +export const mapWorkflow = (workflow: Workflow & { steps: WorkflowStep[] }): WorkflowResponseDto => { return { - id: action.id, - workflowId: action.workflowId, - pluginActionId: action.pluginActionId, - actionConfig: action.actionConfig, - order: action.order, + id: workflow.id, + enabled: workflow.enabled, + trigger: workflow.trigger, + name: workflow.name, + description: workflow.description, + createdAt: workflow.createdAt.toISOString(), + updatedAt: workflow.updatedAt.toISOString(), + steps: workflow.steps.map((step) => ({ + method: `${step.pluginName}#${step.methodName}`, + // TODO fix this + config: step.config as any, + enabled: step.enabled, + })), }; -} +}; + +export const mapWorkflowShare = (workflow: Workflow & { steps: WorkflowStep[] }): WorkflowShareResponseDto => { + return { + trigger: workflow.trigger, + name: workflow.name, + description: workflow.description, + steps: workflow.steps.map((step) => ({ + method: `${step.pluginName}#${step.methodName}`, + // TODO fix this + config: step.config as any, + enabled: step.enabled ? undefined : false, + })), + }; +}; diff --git a/server/src/enum.ts b/server/src/enum.ts index 636db8adab..bc52e65f83 100644 --- a/server/src/enum.ts +++ b/server/src/enum.ts @@ -749,8 +749,11 @@ export enum BootstrapEventPriority { StorageService = -195, // Other services may need to queue jobs on bootstrap. JobService = -190, - // Initialise config after other bootstrap services, stop other services from using config on bootstrap + // Initialize config after other bootstrap services, stop other services from using config on bootstrap SystemConfig = 100, + PluginSync = 190, + // Load plugins into memory after sync + PluginLoad = 200, } export enum QueueName { @@ -863,7 +866,7 @@ export enum JobName { Ocr = 'Ocr', // Workflow - WorkflowRun = 'WorkflowRun', + WorkflowAssetCreate = 'WorkflowAssetCreate', } export const JobNameSchema = z.enum(JobName).describe('Job name').meta({ id: 'JobName' }); @@ -909,6 +912,7 @@ export enum DatabaseLock { CLIPDimSize = 512, Library = 1337, NightlyJobs = 600, + PluginImport = 666, MediaLocation = 700, GetSystemConfig = 69, BackupDatabase = 42, @@ -1160,12 +1164,19 @@ export enum PluginContext { export const PluginContextSchema = z.enum(PluginContext).describe('Plugin context').meta({ id: 'PluginContextType' }); -export enum PluginTriggerType { +export enum WorkflowTrigger { AssetCreate = 'AssetCreate', PersonRecognized = 'PersonRecognized', } -export const PluginTriggerTypeSchema = z - .enum(PluginTriggerType) +export const WorkflowTriggerSchema = z + .enum(WorkflowTrigger) .describe('Plugin trigger type') - .meta({ id: 'PluginTriggerType' }); + .meta({ id: 'WorkflowTrigger' }); + +export enum WorkflowType { + AssetV1 = 'AssetV1', + AssetPersonV1 = 'AssetPersonV1', +} + +export const WorkflowTypeSchema = z.enum(WorkflowType).describe('Workflow type').meta({ id: 'WorkflowType' }); diff --git a/server/src/plugins.ts b/server/src/plugins.ts deleted file mode 100644 index 77f35e79f6..0000000000 --- a/server/src/plugins.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { PluginContext, PluginTriggerType } from 'src/enum'; - -export type PluginTrigger = { - type: PluginTriggerType; - contextType: PluginContext; -}; - -export const pluginTriggers: PluginTrigger[] = [ - { - type: PluginTriggerType.AssetCreate, - contextType: PluginContext.Asset, - }, - { - type: PluginTriggerType.PersonRecognized, - contextType: PluginContext.Person, - }, -]; diff --git a/server/src/queries/plugin.repository.sql b/server/src/queries/plugin.repository.sql index 82c203dafd..2c920b7ab0 100644 --- a/server/src/queries/plugin.repository.sql +++ b/server/src/queries/plugin.repository.sql @@ -1,159 +1,159 @@ -- NOTE: This file is auto generated by ./sql-generator --- PluginRepository.getPlugin +-- PluginRepository.getForLoad select - "plugin"."id" as "id", - "plugin"."name" as "name", - "plugin"."title" as "title", - "plugin"."description" as "description", - "plugin"."author" as "author", - "plugin"."version" as "version", - "plugin"."wasmPath" as "wasmPath", - "plugin"."createdAt" as "createdAt", - "plugin"."updatedAt" as "updatedAt", + "plugin"."id", + "plugin"."name", + "plugin"."version", + "plugin"."wasmBytes", ( select coalesce(json_agg(agg), '[]') from ( select - * + "plugin_method"."name", + "plugin_method"."hostFunctions" from - "plugin_filter" + "plugin_method" where - "plugin_filter"."pluginId" = "plugin"."id" + "plugin_method"."pluginId" = "plugin"."id" ) as agg - ) as "filters", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - * - from - "plugin_action" - where - "plugin_action"."pluginId" = "plugin"."id" - ) as agg - ) as "actions" + ) as "methods" from "plugin" where - "plugin"."id" = $1 + "enabled" = $1 --- PluginRepository.getPluginByName +-- PluginRepository.search select - "plugin"."id" as "id", - "plugin"."name" as "name", - "plugin"."title" as "title", - "plugin"."description" as "description", - "plugin"."author" as "author", - "plugin"."version" as "version", - "plugin"."wasmPath" as "wasmPath", - "plugin"."createdAt" as "createdAt", - "plugin"."updatedAt" as "updatedAt", + "plugin"."id", + "plugin"."name", + "plugin"."title", + "plugin"."description", + "plugin"."author", + "plugin"."version", + "plugin"."createdAt", + "plugin"."updatedAt", ( select coalesce(json_agg(agg), '[]') from ( select - * + "plugin_method"."name", + "plugin_method"."title", + "plugin_method"."description", + "plugin_method"."types", + "plugin_method"."schema", + "plugin_method"."hostFunctions", + "plugin_method"."uiHints", + "plugin"."name" as "pluginName" from - "plugin_filter" + "plugin_method" where - "plugin_filter"."pluginId" = "plugin"."id" + "plugin_method"."pluginId" = "plugin"."id" ) as agg - ) as "filters", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - * - from - "plugin_action" - where - "plugin_action"."pluginId" = "plugin"."id" - ) as agg - ) as "actions" -from - "plugin" -where - "plugin"."name" = $1 - --- PluginRepository.getAllPlugins -select - "plugin"."id" as "id", - "plugin"."name" as "name", - "plugin"."title" as "title", - "plugin"."description" as "description", - "plugin"."author" as "author", - "plugin"."version" as "version", - "plugin"."wasmPath" as "wasmPath", - "plugin"."createdAt" as "createdAt", - "plugin"."updatedAt" as "updatedAt", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - * - from - "plugin_filter" - where - "plugin_filter"."pluginId" = "plugin"."id" - ) as agg - ) as "filters", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - * - from - "plugin_action" - where - "plugin_action"."pluginId" = "plugin"."id" - ) as agg - ) as "actions" + ) as "methods" from "plugin" order by "plugin"."name" --- PluginRepository.getFilter +-- PluginRepository.getByName select - * + "plugin"."id", + "plugin"."name", + "plugin"."title", + "plugin"."description", + "plugin"."author", + "plugin"."version", + "plugin"."createdAt", + "plugin"."updatedAt", + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "plugin_method"."name", + "plugin_method"."title", + "plugin_method"."description", + "plugin_method"."types", + "plugin_method"."schema", + "plugin_method"."hostFunctions", + "plugin_method"."uiHints", + "plugin"."name" as "pluginName" + from + "plugin_method" + where + "plugin_method"."pluginId" = "plugin"."id" + ) as agg + ) as "methods" from - "plugin_filter" + "plugin" where - "id" = $1 + "plugin"."name" = $1 --- PluginRepository.getFiltersByPlugin +-- PluginRepository.get select - * + "plugin"."id", + "plugin"."name", + "plugin"."title", + "plugin"."description", + "plugin"."author", + "plugin"."version", + "plugin"."createdAt", + "plugin"."updatedAt", + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "plugin_method"."name", + "plugin_method"."title", + "plugin_method"."description", + "plugin_method"."types", + "plugin_method"."schema", + "plugin_method"."hostFunctions", + "plugin_method"."uiHints", + "plugin"."name" as "pluginName" + from + "plugin_method" + where + "plugin_method"."pluginId" = "plugin"."id" + ) as agg + ) as "methods" from - "plugin_filter" + "plugin" where - "pluginId" = $1 + "plugin"."id" = $1 --- PluginRepository.getAction +-- PluginRepository.getForValidation select - * + "plugin_method"."id", + "plugin_method"."name", + "plugin"."name" as "pluginName", + "plugin_method"."types" from - "plugin_action" -where - "id" = $1 + "plugin_method" + inner join "plugin" on "plugin_method"."pluginId" = "plugin"."id" --- PluginRepository.getActionsByPlugin +-- PluginRepository.searchMethods select - * + "plugin"."name" as "pluginName", + "plugin_method"."pluginId", + "plugin_method"."id", + "plugin_method"."name", + "plugin_method"."title", + "plugin_method"."description", + "plugin_method"."types", + "plugin_method"."schema", + "plugin_method"."hostFunctions", + "plugin_method"."uiHints" from - "plugin_action" -where - "pluginId" = $1 + "plugin_method" + inner join "plugin" on "plugin"."id" = "plugin_method"."pluginId" +order by + "plugin_method"."name" diff --git a/server/src/queries/workflow.repository.sql b/server/src/queries/workflow.repository.sql index 27dc21dffe..fb62a51a73 100644 --- a/server/src/queries/workflow.repository.sql +++ b/server/src/queries/workflow.repository.sql @@ -1,70 +1,101 @@ -- NOTE: This file is auto generated by ./sql-generator --- WorkflowRepository.getWorkflow +-- WorkflowRepository.search select - * + "workflow"."id", + "workflow"."name", + "workflow"."description", + "workflow"."trigger", + "workflow"."enabled", + "workflow"."createdAt", + "workflow"."updatedAt", + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "plugin"."name" as "pluginName", + "plugin_method"."name" as "methodName", + "workflow_step"."config", + "workflow_step"."enabled" + from + "workflow_step" + inner join "plugin_method" on "plugin_method"."id" = "workflow_step"."pluginMethodId" + inner join "plugin" on "plugin"."id" = "plugin_method"."pluginId" + where + "workflow"."id" = "workflow_step"."workflowId" + ) as agg + ) as "steps" +from + "workflow" +order by + "createdAt" desc + +-- WorkflowRepository.get +select + "workflow"."id", + "workflow"."name", + "workflow"."description", + "workflow"."trigger", + "workflow"."enabled", + "workflow"."createdAt", + "workflow"."updatedAt", + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "plugin"."name" as "pluginName", + "plugin_method"."name" as "methodName", + "workflow_step"."config", + "workflow_step"."enabled" + from + "workflow_step" + inner join "plugin_method" on "plugin_method"."id" = "workflow_step"."pluginMethodId" + inner join "plugin" on "plugin"."id" = "plugin_method"."pluginId" + where + "workflow"."id" = "workflow_step"."workflowId" + ) as agg + ) as "steps" from "workflow" where "id" = $1 -order by - "createdAt" desc --- WorkflowRepository.getWorkflowsByOwner +-- WorkflowRepository.getForWorkflowRun select - * + "workflow"."id", + "workflow"."name", + "workflow"."trigger", + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "workflow_step"."id", + "workflow_step"."config", + "plugin_method"."pluginId" as "pluginId", + "plugin_method"."name" as "methodName", + "plugin_method"."types" as "types", + "plugin_method"."hostFunctions" + from + "workflow_step" + inner join "plugin_method" on "plugin_method"."id" = "workflow_step"."pluginMethodId" + where + "workflow_step"."workflowId" = "workflow"."id" + and "workflow_step"."enabled" = $1 + ) as agg + ) as "steps" from "workflow" where - "ownerId" = $1 -order by - "createdAt" desc - --- WorkflowRepository.getWorkflowsByTrigger -select - * -from - "workflow" -where - "triggerType" = $1 - and "enabled" = $2 - --- WorkflowRepository.getWorkflowByOwnerAndTrigger -select - * -from - "workflow" -where - "ownerId" = $1 - and "triggerType" = $2 + "id" = $2 and "enabled" = $3 --- WorkflowRepository.deleteWorkflow +-- WorkflowRepository.delete delete from "workflow" where "id" = $1 - --- WorkflowRepository.getFilters -select - * -from - "workflow_filter" -where - "workflowId" = $1 -order by - "order" asc - --- WorkflowRepository.deleteFiltersByWorkflow -delete from "workflow_filter" -where - "workflowId" = $1 - --- WorkflowRepository.getActions -select - * -from - "workflow_action" -where - "workflowId" = $1 -order by - "order" asc diff --git a/server/src/repositories/config.repository.ts b/server/src/repositories/config.repository.ts index 240197e9ab..f88deca09d 100644 --- a/server/src/repositories/config.repository.ts +++ b/server/src/repositories/config.repository.ts @@ -346,7 +346,7 @@ const getEnv = (): EnvData => { root: folders.web, indexHtml: join(folders.web, 'index.html'), }, - corePlugin: join(buildFolder, 'corePlugin'), + corePlugin: join(buildFolder, 'plugins', 'immich-plugin-core'), }, setup: { diff --git a/server/src/repositories/job.repository.ts b/server/src/repositories/job.repository.ts index a94e5aa9f6..5bb5276db7 100644 --- a/server/src/repositories/job.repository.ts +++ b/server/src/repositories/job.repository.ts @@ -171,8 +171,8 @@ export class JobRepository { options: this.getJobOptions(item) || undefined, } as JobItem & { data: any; options: JobsOptions | undefined }; - if (job.options?.jobId) { - // need to use add() instead of addBulk() for jobId deduplication + if (job.options?.jobId || job.options?.deduplication) { + // need to use add() instead of addBulk() for jobId/deduplication to take effect promises.push(this.getQueue(queueName).add(item.name, item.data, job.options)); } else { itemsByQueue[queueName] = itemsByQueue[queueName] || []; @@ -230,10 +230,13 @@ export class JobRepository { return { priority: 1 }; } case JobName.FacialRecognitionQueueAll: { - return { jobId: JobName.FacialRecognitionQueueAll }; + return { deduplication: { id: JobName.FacialRecognitionQueueAll } }; } case JobName.VersionCheck: { - return { jobId: JobName.VersionCheck }; + return { deduplication: { id: JobName.VersionCheck } }; + } + case JobName.DatabaseBackup: { + return { deduplication: { id: JobName.DatabaseBackup } }; } default: { return null; diff --git a/server/src/repositories/logging.repository.ts b/server/src/repositories/logging.repository.ts index 39867b14d0..c1df648f09 100644 --- a/server/src/repositories/logging.repository.ts +++ b/server/src/repositories/logging.repository.ts @@ -119,6 +119,10 @@ export class LoggingRepository { logLevels = level ? LOG_LEVELS.slice(LOG_LEVELS.indexOf(level)) : []; } + getLogLevel(): LogLevel { + return logLevels[0] || LogLevel.Fatal; + } + verbose(message: string, ...details: LogDetails) { this.handleMessage(LogLevel.Verbose, message, details); } diff --git a/server/src/repositories/machine-learning.repository.ts b/server/src/repositories/machine-learning.repository.ts index 49778b5193..6792e8ecf4 100644 --- a/server/src/repositories/machine-learning.repository.ts +++ b/server/src/repositories/machine-learning.repository.ts @@ -132,7 +132,7 @@ export class MachineLearningRepository { private async check(url: string) { let healthy = false; try { - const response = await fetch(new URL('/ping', url), { + const response = await fetch(new URL('ping', url), { signal: AbortSignal.timeout(this.config.availabilityChecks.timeout), }); if (response.ok) { @@ -170,7 +170,7 @@ export class MachineLearningRepository { ...this.config.urls.filter((url) => !this.isHealthy(url)), ]) { try { - const response = await fetch(new URL('/predict', url), { method: 'POST', body: formData }); + const response = await fetch(new URL('predict', url), { method: 'POST', body: formData }); if (response.ok) { this.setHealthy(url, true); return response.json(); diff --git a/server/src/repositories/plugin.repository.ts b/server/src/repositories/plugin.repository.ts index 6217237947..a07f433541 100644 --- a/server/src/repositories/plugin.repository.ts +++ b/server/src/repositories/plugin.repository.ts @@ -1,176 +1,254 @@ +import { CallContext, Plugin as ExtismPlugin, newPlugin } from '@extism/extism'; import { Injectable } from '@nestjs/common'; -import { Kysely } from 'kysely'; +import { createPool, Pool } from 'generic-pool'; +import { Insertable, Kysely } from 'kysely'; import { jsonArrayFrom } from 'kysely/helpers/postgres'; import { InjectKysely } from 'nestjs-kysely'; -import { readdir } from 'node:fs/promises'; import { columns } from 'src/database'; import { DummyValue, GenerateSql } from 'src/decorators'; -import { PluginManifestDto } from 'src/dtos/plugin-manifest.dto'; +import { PluginMethodSearchDto, PluginSearchDto } from 'src/dtos/plugin.dto'; +import { LogLevel, WorkflowType } from 'src/enum'; +import { LoggingRepository } from 'src/repositories/logging.repository'; import { DB } from 'src/schema'; +import { PluginMethodTable } from 'src/schema/tables/plugin-method.table'; +import { PluginTable } from 'src/schema/tables/plugin.table'; + +type PluginMethod = { pluginKey: string; methodName: string }; +type PluginLoad = { key: string; label: string; wasmBytes: Buffer }; + +export type PluginHostFunction = (callContext: CallContext, input: bigint) => Promise | bigint; +export type PluginLoadOptions = { + runInWorker?: boolean; + functions?: Record; +}; + +export type PluginMethodSearchResponse = { + id: string; + name: string; + pluginName: string; + types: WorkflowType[]; +}; + +const levels = { + [LogLevel.Verbose]: 'trace', + [LogLevel.Debug]: 'debug', + [LogLevel.Log]: 'info', + [LogLevel.Warn]: 'warn', + [LogLevel.Error]: 'error', + [LogLevel.Fatal]: 'error', +} as const; + +const asExtismLogLevel = (logLevel: LogLevel) => levels[logLevel] || 'info'; @Injectable() export class PluginRepository { - constructor(@InjectKysely() private db: Kysely) {} + private pluginMap: Map }> = new Map(); - /** - * Loads a plugin from a validated manifest file in a transaction. - * This ensures all plugin, filter, and action operations are atomic. - * @param manifest The validated plugin manifest - * @param basePath The base directory path where the plugin is located - */ - async loadPlugin(manifest: PluginManifestDto, basePath: string) { - return this.db.transaction().execute(async (tx) => { - // Upsert the plugin - const plugin = await tx - .insertInto('plugin') - .values({ - name: manifest.name, - title: manifest.title, - description: manifest.description, - author: manifest.author, - version: manifest.version, - wasmPath: `${basePath}/${manifest.wasm.path}`, - }) - .onConflict((oc) => - oc.column('name').doUpdateSet({ - title: manifest.title, - description: manifest.description, - author: manifest.author, - version: manifest.version, - wasmPath: `${basePath}/${manifest.wasm.path}`, - }), - ) - .returningAll() - .executeTakeFirstOrThrow(); - - const filters = manifest.filters - ? await tx - .insertInto('plugin_filter') - .values( - manifest.filters.map((filter) => ({ - pluginId: plugin.id, - methodName: filter.methodName, - title: filter.title, - description: filter.description, - supportedContexts: filter.supportedContexts, - schema: filter.schema, - })), - ) - .onConflict((oc) => - oc.column('methodName').doUpdateSet((eb) => ({ - pluginId: eb.ref('excluded.pluginId'), - title: eb.ref('excluded.title'), - description: eb.ref('excluded.description'), - supportedContexts: eb.ref('excluded.supportedContexts'), - schema: eb.ref('excluded.schema'), - })), - ) - .returningAll() - .execute() - : []; - - const actions = manifest.actions - ? await tx - .insertInto('plugin_action') - .values( - manifest.actions.map((action) => ({ - pluginId: plugin.id, - methodName: action.methodName, - title: action.title, - description: action.description, - supportedContexts: action.supportedContexts, - schema: action.schema, - })), - ) - .onConflict((oc) => - oc.column('methodName').doUpdateSet((eb) => ({ - pluginId: eb.ref('excluded.pluginId'), - title: eb.ref('excluded.title'), - description: eb.ref('excluded.description'), - supportedContexts: eb.ref('excluded.supportedContexts'), - schema: eb.ref('excluded.schema'), - })), - ) - .returningAll() - .execute() - : []; - - return { plugin, filters, actions }; - }); - } - - async readDirectory(path: string) { - return readdir(path, { withFileTypes: true }); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getPlugin(id: string) { - return this.db - .selectFrom('plugin') - .select((eb) => [ - ...columns.plugin, - jsonArrayFrom( - eb.selectFrom('plugin_filter').selectAll().whereRef('plugin_filter.pluginId', '=', 'plugin.id'), - ).as('filters'), - jsonArrayFrom( - eb.selectFrom('plugin_action').selectAll().whereRef('plugin_action.pluginId', '=', 'plugin.id'), - ).as('actions'), - ]) - .where('plugin.id', '=', id) - .executeTakeFirst(); - } - - @GenerateSql({ params: [DummyValue.STRING] }) - getPluginByName(name: string) { - return this.db - .selectFrom('plugin') - .select((eb) => [ - ...columns.plugin, - jsonArrayFrom( - eb.selectFrom('plugin_filter').selectAll().whereRef('plugin_filter.pluginId', '=', 'plugin.id'), - ).as('filters'), - jsonArrayFrom( - eb.selectFrom('plugin_action').selectAll().whereRef('plugin_action.pluginId', '=', 'plugin.id'), - ).as('actions'), - ]) - .where('plugin.name', '=', name) - .executeTakeFirst(); + constructor( + @InjectKysely() private db: Kysely, + private logger: LoggingRepository, + ) { + this.logger.setContext(PluginRepository.name); } @GenerateSql() - getAllPlugins() { + getForLoad() { return this.db .selectFrom('plugin') .select((eb) => [ - ...columns.plugin, + 'plugin.id', + 'plugin.name', + 'plugin.version', + 'plugin.wasmBytes', jsonArrayFrom( - eb.selectFrom('plugin_filter').selectAll().whereRef('plugin_filter.pluginId', '=', 'plugin.id'), - ).as('filters'), - jsonArrayFrom( - eb.selectFrom('plugin_action').selectAll().whereRef('plugin_action.pluginId', '=', 'plugin.id'), - ).as('actions'), + eb + .selectFrom('plugin_method') + .whereRef('plugin_method.pluginId', '=', 'plugin.id') + .select(['plugin_method.name', 'plugin_method.hostFunctions']), + ).as('methods'), ]) + .where('enabled', '=', true) + .execute(); + } + + private queryBuilder() { + return this.db.selectFrom('plugin').select((eb) => [ + 'plugin.id', + 'plugin.name', + 'plugin.title', + 'plugin.description', + 'plugin.author', + 'plugin.version', + 'plugin.createdAt', + 'plugin.updatedAt', + jsonArrayFrom( + eb + .selectFrom('plugin_method') + .select([...columns.pluginMethod, 'plugin.name as pluginName']) + .whereRef('plugin_method.pluginId', '=', 'plugin.id'), + ).as('methods'), + ]); + } + + @GenerateSql() + search(dto: PluginSearchDto = {}) { + return this.queryBuilder() + .$if(!!dto.id, (qb) => qb.where('plugin.id', '=', dto.id!)) + .$if(!!dto.name, (qb) => qb.where('plugin.name', '=', dto.name!)) + .$if(!!dto.title, (qb) => qb.where('plugin.title', '=', dto.title!)) + .$if(!!dto.description, (qb) => qb.where('plugin.description', '=', dto.description!)) + .$if(!!dto.version, (qb) => qb.where('plugin.version', '=', dto.version!)) .orderBy('plugin.name') .execute(); } - @GenerateSql({ params: [DummyValue.UUID] }) - getFilter(id: string) { - return this.db.selectFrom('plugin_filter').selectAll().where('id', '=', id).executeTakeFirst(); + @GenerateSql({ params: [DummyValue.STRING] }) + getByName(name: string) { + return this.queryBuilder().where('plugin.name', '=', name).executeTakeFirst(); } @GenerateSql({ params: [DummyValue.UUID] }) - getFiltersByPlugin(pluginId: string) { - return this.db.selectFrom('plugin_filter').selectAll().where('pluginId', '=', pluginId).execute(); + get(id: string) { + return this.queryBuilder().where('plugin.id', '=', id).executeTakeFirst(); } - @GenerateSql({ params: [DummyValue.UUID] }) - getAction(id: string) { - return this.db.selectFrom('plugin_action').selectAll().where('id', '=', id).executeTakeFirst(); + @GenerateSql() + getForValidation(): Promise { + return this.db + .selectFrom('plugin_method') + .innerJoin('plugin', 'plugin_method.pluginId', 'plugin.id') + .select(['plugin_method.id', 'plugin_method.name', 'plugin.name as pluginName', 'plugin_method.types']) + .execute(); } - @GenerateSql({ params: [DummyValue.UUID] }) - getActionsByPlugin(pluginId: string) { - return this.db.selectFrom('plugin_action').selectAll().where('pluginId', '=', pluginId).execute(); + @GenerateSql() + searchMethods(dto: PluginMethodSearchDto = {}) { + return this.db + .selectFrom('plugin_method') + .innerJoin('plugin', 'plugin.id', 'plugin_method.pluginId') + .select(['plugin.name as pluginName', 'plugin_method.pluginId', 'plugin_method.id', ...columns.pluginMethod]) + .$if(!!dto.id, (qb) => qb.where('plugin_method.id', '=', dto.id!)) + .$if(!!dto.name, (qb) => qb.where('plugin_method.name', '=', dto.name!)) + .$if(!!dto.title, (qb) => qb.where('plugin_method.title', '=', dto.title!)) + .$if(!!dto.type, (qb) => qb.where('plugin_method.types', '@>', [dto.type!])) + .$if(!!dto.description, (qb) => qb.where('plugin_method.description', '=', dto.description!)) + .$if(!!dto.pluginVersion, (qb) => qb.where('plugin.version', '=', dto.pluginVersion!)) + .$if(!!dto.pluginName, (qb) => qb.where('plugin.name', '=', dto.pluginName!)) + .orderBy('plugin_method.name') + .execute(); + } + + async upsert(dto: Insertable, initialMethods: Omit, 'pluginId'>[]) { + return this.db.transaction().execute(async (tx) => { + // Upsert the plugin + const plugin = await tx + .insertInto('plugin') + .values(dto) + .onConflict((oc) => + oc.columns(['name', 'version']).doUpdateSet((eb) => ({ + title: eb.ref('excluded.title'), + description: eb.ref('excluded.description'), + author: eb.ref('excluded.author'), + version: eb.ref('excluded.version'), + wasmBytes: eb.ref('excluded.wasmBytes'), + })), + ) + .returning(['id', 'name']) + .executeTakeFirstOrThrow(); + + // prune methods that no longer exist + if (initialMethods.length > 0) { + await tx + .deleteFrom('plugin_method') + .where('plugin_method.pluginId', '=', plugin.id) + .where( + 'name', + 'not in', + initialMethods.map((method) => method.name), + ) + .execute(); + } + + const methods = + initialMethods.length > 0 + ? await tx + .insertInto('plugin_method') + .values(initialMethods.map((method) => ({ ...method, pluginId: plugin.id }))) + .onConflict((oc) => + oc.columns(['pluginId', 'name']).doUpdateSet(({ ref }) => ({ + pluginId: ref('excluded.pluginId'), + name: ref('excluded.name'), + title: ref('excluded.title'), + description: ref('excluded.description'), + types: ref('excluded.types'), + hostFunctions: ref('excluded.hostFunctions'), + uiHints: ref('excluded.uiHints'), + schema: ref('excluded.schema'), + })), + ) + .returningAll() + .execute() + : []; + + return { ...plugin, methods }; + }); + } + + async load({ key, label, wasmBytes }: PluginLoad, { runInWorker, functions }: PluginLoadOptions) { + const data = new Uint8Array(wasmBytes.buffer, wasmBytes.byteOffset, wasmBytes.byteLength); + const logger = LoggingRepository.create(`Plugin:${label}`); + const pool = createPool( + { + create: () => + newPlugin( + { wasm: [{ data }] }, + { + useWasi: true, + runInWorker, + functions: { + 'extism:host/user': functions ?? {}, + }, + logger: { + trace: (message) => logger.verbose(message), + info: (message) => logger.log(message), + debug: (message) => logger.debug(message), + warn: (message) => logger.warn(message), + error: (message) => logger.error(message), + } as Console, + logLevel: asExtismLogLevel(logger.getLogLevel()), + }, + ), + destroy: (plugin) => plugin.close(), + }, + { min: 1, max: 5 }, + ); + + try { + await pool.ready(); + this.pluginMap.set(key, { pool, label }); + } catch (error: Error | any) { + throw new Error(`Unable to instantiate plugin: ${key}`, { cause: error }); + } + } + + async callMethod({ pluginKey, methodName }: PluginMethod, input: unknown) { + const item = this.pluginMap.get(pluginKey); + if (!item) { + throw new Error(`No loaded plugin found for ${pluginKey}`); + } + + const { pool, label } = item; + + try { + const plugin = await pool.acquire(); + try { + const result = await plugin.call(methodName, JSON.stringify(input)); + return (result ? result.json() : result) as T; + } finally { + await pool.release(plugin); + } + } catch (error: Error | any) { + throw new Error(`Plugin method call failed: ${label}#${methodName}`, { cause: error }); + } } } diff --git a/server/src/repositories/storage.repository.ts b/server/src/repositories/storage.repository.ts index c7ba4ab6cc..1d3971fd28 100644 --- a/server/src/repositories/storage.repository.ts +++ b/server/src/repositories/storage.repository.ts @@ -2,7 +2,15 @@ import { Injectable } from '@nestjs/common'; import archiver from 'archiver'; import chokidar, { ChokidarOptions } from 'chokidar'; import { escapePath, glob, globStream } from 'fast-glob'; -import { constants, createReadStream, createWriteStream, existsSync, mkdirSync, ReadOptionsWithBuffer } from 'node:fs'; +import { + constants, + createReadStream, + createWriteStream, + Dirent, + existsSync, + mkdirSync, + ReadOptionsWithBuffer, +} from 'node:fs'; import fs from 'node:fs/promises'; import path from 'node:path'; import { PassThrough, Readable, Writable } from 'node:stream'; @@ -50,6 +58,10 @@ export class StorageRepository { return fs.readdir(folder); } + readdirWithTypes(folder: string): Promise { + return fs.readdir(folder, { withFileTypes: true }); + } + copyFile(source: string, target: string) { return fs.copyFile(source, target); } @@ -117,17 +129,24 @@ export class StorageRepository { } async readFile(filepath: string, options?: ReadOptionsWithBuffer): Promise { - const file = await fs.open(filepath); - try { - const { buffer } = await file.read(options); - return buffer as Buffer; - } finally { - await file.close(); + // read a slice + if (options) { + const file = await fs.open(filepath); + try { + const { buffer } = await file.read(options); + return buffer as Buffer; + } finally { + await file.close(); + } } + + // read everything + return fs.readFile(filepath); } - async readTextFile(filepath: string): Promise { - return fs.readFile(filepath, 'utf8'); + async readJsonFile(filepath: string): Promise { + const file = await fs.readFile(filepath, 'utf8'); + return JSON.parse(file) as T; } async checkFileExists(filepath: string, mode = constants.F_OK): Promise { diff --git a/server/src/repositories/workflow.repository.ts b/server/src/repositories/workflow.repository.ts index deaf2aa2fc..cd5aa6431f 100644 --- a/server/src/repositories/workflow.repository.ts +++ b/server/src/repositories/workflow.repository.ts @@ -1,149 +1,180 @@ import { Injectable } from '@nestjs/common'; import { Insertable, Kysely, Updateable } from 'kysely'; +import { jsonArrayFrom, jsonObjectFrom } from 'kysely/helpers/postgres'; import { InjectKysely } from 'nestjs-kysely'; +import { columns } from 'src/database'; import { DummyValue, GenerateSql } from 'src/decorators'; -import { PluginTriggerType } from 'src/enum'; +import { WorkflowSearchDto } from 'src/dtos/workflow.dto'; import { DB } from 'src/schema'; -import { WorkflowActionTable, WorkflowFilterTable, WorkflowTable } from 'src/schema/tables/workflow.table'; +import { WorkflowStepTable } from 'src/schema/tables/workflow-step.table'; +import { WorkflowTable } from 'src/schema/tables/workflow.table'; + +export type WorkflowStepUpsert = Omit, 'workflowId' | 'order'>; @Injectable() export class WorkflowRepository { constructor(@InjectKysely() private db: Kysely) {} + private queryBuilder(db?: Kysely) { + return (db ?? this.db) + .selectFrom('workflow') + .select([ + 'workflow.id', + 'workflow.name', + 'workflow.description', + 'workflow.trigger', + 'workflow.enabled', + 'workflow.createdAt', + 'workflow.updatedAt', + ]) + .select((eb) => [ + jsonArrayFrom( + eb + .selectFrom('workflow_step') + .innerJoin('plugin_method', 'plugin_method.id', 'workflow_step.pluginMethodId') + .innerJoin('plugin', 'plugin.id', 'plugin_method.pluginId') + .whereRef('workflow.id', '=', 'workflow_step.workflowId') + .select([ + 'plugin.name as pluginName', + 'plugin_method.name as methodName', + 'workflow_step.config', + 'workflow_step.enabled', + ]), + ).as('steps'), + ]); + } + @GenerateSql({ params: [DummyValue.UUID] }) - getWorkflow(id: string) { + search(dto: WorkflowSearchDto & { ownerId?: string }) { + return this.queryBuilder() + .$if(!!dto.ownerId, (qb) => qb.where('ownerId', '=', dto.ownerId!)) + .$if(!!dto.trigger, (qb) => qb.where('trigger', '=', dto.trigger!)) + .$if(dto.enabled !== undefined, (qb) => qb.where('enabled', '=', dto.enabled!)) + .orderBy('createdAt', 'desc') + .execute(); + } + + @GenerateSql({ params: [DummyValue.UUID] }) + get(id: string) { + return this.queryBuilder().where('id', '=', id).executeTakeFirst(); + } + + @GenerateSql({ params: [DummyValue.UUID] }) + getForWorkflowRun(id: string) { return this.db .selectFrom('workflow') - .selectAll() + .select(['workflow.id', 'workflow.name', 'workflow.trigger']) + .select((eb) => [ + jsonArrayFrom( + eb + .selectFrom('workflow_step') + .innerJoin('plugin_method', 'plugin_method.id', 'workflow_step.pluginMethodId') + .whereRef('workflow_step.workflowId', '=', 'workflow.id') + .where('workflow_step.enabled', '=', true) + .select([ + 'workflow_step.id', + 'workflow_step.config', + 'plugin_method.pluginId as pluginId', + 'plugin_method.name as methodName', + 'plugin_method.types as types', + 'plugin_method.hostFunctions', + ]), + ).as('steps'), + ]) .where('id', '=', id) - .orderBy('createdAt', 'desc') + .where('enabled', '=', true) .executeTakeFirst(); } - @GenerateSql({ params: [DummyValue.UUID] }) - getWorkflowsByOwner(ownerId: string) { - return this.db - .selectFrom('workflow') - .selectAll() - .where('ownerId', '=', ownerId) - .orderBy('createdAt', 'desc') - .execute(); - } - - @GenerateSql({ params: [PluginTriggerType.AssetCreate] }) - getWorkflowsByTrigger(type: PluginTriggerType) { - return this.db - .selectFrom('workflow') - .selectAll() - .where('triggerType', '=', type) - .where('enabled', '=', true) - .execute(); - } - - @GenerateSql({ params: [DummyValue.UUID, PluginTriggerType.AssetCreate] }) - getWorkflowByOwnerAndTrigger(ownerId: string, type: PluginTriggerType) { - return this.db - .selectFrom('workflow') - .selectAll() - .where('ownerId', '=', ownerId) - .where('triggerType', '=', type) - .where('enabled', '=', true) - .execute(); - } - - async createWorkflow( - workflow: Insertable, - filters: Insertable[], - actions: Insertable[], - ) { - return await this.db.transaction().execute(async (tx) => { - const createdWorkflow = await tx.insertInto('workflow').values(workflow).returningAll().executeTakeFirstOrThrow(); - - if (filters.length > 0) { - const newFilters = filters.map((filter) => ({ - ...filter, - workflowId: createdWorkflow.id, - })); - - await tx.insertInto('workflow_filter').values(newFilters).execute(); - } - - if (actions.length > 0) { - const newActions = actions.map((action) => ({ - ...action, - workflowId: createdWorkflow.id, - })); - await tx.insertInto('workflow_action').values(newActions).execute(); - } - - return createdWorkflow; + create(dto: Insertable, steps?: WorkflowStepUpsert[]) { + return this.db.transaction().execute(async (tx) => { + const { id } = await tx.insertInto('workflow').values(dto).returning(['id']).executeTakeFirstOrThrow(); + return this.replaceAndReturn(tx, id, steps); }); } - async updateWorkflow( - id: string, - workflow: Updateable, - filters: Insertable[] | undefined, - actions: Insertable[] | undefined, - ) { - return await this.db.transaction().execute(async (trx) => { - if (Object.keys(workflow).length > 0) { - await trx.updateTable('workflow').set(workflow).where('id', '=', id).execute(); + update(id: string, dto: Updateable, steps?: WorkflowStepUpsert[]) { + return this.db.transaction().execute(async (tx) => { + if (Object.values(dto).some((prop) => prop !== undefined)) { + await tx.updateTable('workflow').set(dto).where('id', '=', id).executeTakeFirstOrThrow(); } - - if (filters !== undefined) { - await trx.deleteFrom('workflow_filter').where('workflowId', '=', id).execute(); - if (filters.length > 0) { - const filtersWithWorkflowId = filters.map((filter) => ({ - ...filter, - workflowId: id, - })); - await trx.insertInto('workflow_filter').values(filtersWithWorkflowId).execute(); - } - } - - if (actions !== undefined) { - await trx.deleteFrom('workflow_action').where('workflowId', '=', id).execute(); - if (actions.length > 0) { - const actionsWithWorkflowId = actions.map((action) => ({ - ...action, - workflowId: id, - })); - await trx.insertInto('workflow_action').values(actionsWithWorkflowId).execute(); - } - } - - return await trx.selectFrom('workflow').selectAll().where('id', '=', id).executeTakeFirstOrThrow(); + return this.replaceAndReturn(tx, id, steps); }); } + private async replaceAndReturn(tx: Kysely, workflowId: string, steps?: WorkflowStepUpsert[]) { + if (steps) { + await tx.deleteFrom('workflow_step').where('workflowId', '=', workflowId).execute(); + if (steps.length > 0) { + await tx + .insertInto('workflow_step') + .values( + steps.map((step, i) => ({ + workflowId, + enabled: step.enabled ?? true, + pluginMethodId: step.pluginMethodId, + config: step.config, + order: i, + })), + ) + .returningAll() + .execute(); + } + } + + return this.queryBuilder(tx).where('id', '=', workflowId).executeTakeFirstOrThrow(); + } + @GenerateSql({ params: [DummyValue.UUID] }) - async deleteWorkflow(id: string) { + async delete(id: string) { await this.db.deleteFrom('workflow').where('id', '=', id).execute(); } - @GenerateSql({ params: [DummyValue.UUID] }) - getFilters(workflowId: string) { + getForAssetV1(assetId: string) { return this.db - .selectFrom('workflow_filter') - .selectAll() - .where('workflowId', '=', workflowId) - .orderBy('order', 'asc') - .execute(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - async deleteFiltersByWorkflow(workflowId: string) { - await this.db.deleteFrom('workflow_filter').where('workflowId', '=', workflowId).execute(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getActions(workflowId: string) { - return this.db - .selectFrom('workflow_action') - .selectAll() - .where('workflowId', '=', workflowId) - .orderBy('order', 'asc') - .execute(); + .selectFrom('asset') + .leftJoin('asset_exif', 'asset_exif.assetId', 'asset.id') + .select((eb) => [ + ...columns.workflowAssetV1, + jsonObjectFrom( + eb + .selectFrom('asset_exif') + .select([ + 'asset_exif.make', + 'asset_exif.model', + 'asset_exif.orientation', + 'asset_exif.dateTimeOriginal', + 'asset_exif.modifyDate', + 'asset_exif.exifImageWidth', + 'asset_exif.exifImageHeight', + 'asset_exif.fileSizeInByte', + 'asset_exif.lensModel', + 'asset_exif.fNumber', + 'asset_exif.focalLength', + 'asset_exif.iso', + 'asset_exif.latitude', + 'asset_exif.longitude', + 'asset_exif.city', + 'asset_exif.state', + 'asset_exif.country', + 'asset_exif.description', + 'asset_exif.fps', + 'asset_exif.exposureTime', + 'asset_exif.livePhotoCID', + 'asset_exif.timeZone', + 'asset_exif.projectionType', + 'asset_exif.profileDescription', + 'asset_exif.colorspace', + 'asset_exif.bitsPerSample', + 'asset_exif.autoStackId', + 'asset_exif.rating', + 'asset_exif.tags', + 'asset_exif.updatedAt', + ]) + .whereRef('asset_exif.assetId', '=', 'asset.id'), + ).as('exifInfo'), + ]) + .where('id', '=', assetId) + .executeTakeFirstOrThrow(); } } diff --git a/server/src/schema/index.ts b/server/src/schema/index.ts index 3bb7caf5ff..033f0acd11 100644 --- a/server/src/schema/index.ts +++ b/server/src/schema/index.ts @@ -60,7 +60,8 @@ import { PartnerAuditTable } from 'src/schema/tables/partner-audit.table'; import { PartnerTable } from 'src/schema/tables/partner.table'; import { PersonAuditTable } from 'src/schema/tables/person-audit.table'; import { PersonTable } from 'src/schema/tables/person.table'; -import { PluginActionTable, PluginFilterTable, PluginTable } from 'src/schema/tables/plugin.table'; +import { PluginMethodTable } from 'src/schema/tables/plugin-method.table'; +import { PluginTable } from 'src/schema/tables/plugin.table'; import { SessionTable } from 'src/schema/tables/session.table'; import { SharedLinkAssetTable } from 'src/schema/tables/shared-link-asset.table'; import { SharedLinkTable } from 'src/schema/tables/shared-link.table'; @@ -82,7 +83,8 @@ import { VideoStreamSessionTable, VideoStreamVariantTable, } from 'src/schema/tables/video-stream.table'; -import { WorkflowActionTable, WorkflowFilterTable, WorkflowTable } from 'src/schema/tables/workflow.table'; +import { WorkflowStepTable } from 'src/schema/tables/workflow-step.table'; +import { WorkflowTable } from 'src/schema/tables/workflow.table'; @Extensions(['uuid-ossp', 'unaccent', 'cube', 'earthdistance', 'pg_trgm', 'plpgsql']) @Database({ name: 'immich' }) @@ -143,11 +145,9 @@ export class ImmichDatabase { VideoStreamVariantTable, VideoStreamSegmentTable, PluginTable, - PluginFilterTable, - PluginActionTable, + PluginMethodTable, WorkflowTable, - WorkflowFilterTable, - WorkflowActionTable, + WorkflowStepTable, ]; functions = [ @@ -264,10 +264,8 @@ export interface DB { video_stream_segment: VideoStreamSegmentTable; plugin: PluginTable; - plugin_filter: PluginFilterTable; - plugin_action: PluginActionTable; + plugin_method: PluginMethodTable; workflow: WorkflowTable; - workflow_filter: WorkflowFilterTable; - workflow_action: WorkflowActionTable; + workflow_step: WorkflowStepTable; } diff --git a/server/src/schema/migrations/1778614946174-UpdateWorkflowTables.ts b/server/src/schema/migrations/1778614946174-UpdateWorkflowTables.ts new file mode 100644 index 0000000000..087c9a55ab --- /dev/null +++ b/server/src/schema/migrations/1778614946174-UpdateWorkflowTables.ts @@ -0,0 +1,83 @@ +import { Kysely, sql } from 'kysely'; + +export async function up(db: Kysely): Promise { + // take #2... + await sql`DROP TABLE "workflow_action";`.execute(db); + await sql`DROP TABLE "workflow_filter";`.execute(db); + await sql`DROP TABLE "workflow";`.execute(db); + await sql`DROP TABLE "plugin_action";`.execute(db); + await sql`DROP TABLE "plugin_filter";`.execute(db); + await sql`DROP TABLE "plugin";`.execute(db); + + await sql`CREATE TABLE "plugin" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "enabled" boolean NOT NULL DEFAULT true, + "name" character varying NOT NULL, + "version" character varying NOT NULL, + "title" character varying NOT NULL, + "description" character varying NOT NULL, + "author" character varying NOT NULL, + "wasmBytes" bytea NOT NULL, + "createdAt" timestamp with time zone NOT NULL DEFAULT now(), + "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), + CONSTRAINT "plugin_name_version_uq" UNIQUE ("name", "version"), + CONSTRAINT "plugin_name_uq" UNIQUE ("name"), + CONSTRAINT "plugin_pkey" PRIMARY KEY ("id") +);`.execute(db); + await sql`CREATE INDEX "plugin_name_idx" ON "plugin" ("name");`.execute(db); + await sql`CREATE TABLE "plugin_method" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "pluginId" uuid NOT NULL, + "name" character varying NOT NULL, + "title" character varying NOT NULL, + "description" character varying NOT NULL, + "types" character varying[] NOT NULL, + "hostFunctions" boolean NOT NULL DEFAULT false, + "uiHints" character varying[] NOT NULL DEFAULT '{}', + "schema" jsonb, + CONSTRAINT "plugin_method_pluginId_fkey" FOREIGN KEY ("pluginId") REFERENCES "plugin" ("id") ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT "plugin_method_pluginId_name_uq" UNIQUE ("pluginId", "name"), + CONSTRAINT "plugin_method_pkey" PRIMARY KEY ("id") +);`.execute(db); + await sql`CREATE INDEX "plugin_method_pluginId_idx" ON "plugin_method" ("pluginId");`.execute(db); + await sql`CREATE TABLE "workflow" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "ownerId" uuid NOT NULL, + "trigger" character varying NOT NULL, + "name" character varying, + "description" character varying, + "createdAt" timestamp with time zone NOT NULL DEFAULT now(), + "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), + "updateId" uuid NOT NULL DEFAULT immich_uuid_v7(), + "enabled" boolean NOT NULL DEFAULT true, + CONSTRAINT "workflow_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "user" ("id") ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT "workflow_pkey" PRIMARY KEY ("id") +);`.execute(db); + await sql`CREATE INDEX "workflow_ownerId_idx" ON "workflow" ("ownerId");`.execute(db); + await sql`CREATE OR REPLACE TRIGGER "workflow_updatedAt" + BEFORE UPDATE ON "workflow" + FOR EACH ROW + EXECUTE FUNCTION updated_at();`.execute(db); + await sql`CREATE TABLE "workflow_step" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "enabled" boolean NOT NULL DEFAULT true, + "workflowId" uuid NOT NULL, + "pluginMethodId" uuid NOT NULL, + "config" jsonb, + "order" integer NOT NULL, + CONSTRAINT "workflow_step_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow" ("id") ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT "workflow_step_pluginMethodId_fkey" FOREIGN KEY ("pluginMethodId") REFERENCES "plugin_method" ("id") ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT "workflow_step_pkey" PRIMARY KEY ("id") +);`.execute(db); + await sql`CREATE INDEX "workflow_step_workflowId_idx" ON "workflow_step" ("workflowId");`.execute(db); + await sql`CREATE INDEX "workflow_step_pluginMethodId_idx" ON "workflow_step" ("pluginMethodId");`.execute(db); + await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_workflow_updatedAt', '{"type":"trigger","name":"workflow_updatedAt","sql":"CREATE OR REPLACE TRIGGER \\"workflow_updatedAt\\"\\n BEFORE UPDATE ON \\"workflow\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute( + db, + ); + await sql`DELETE FROM "migration_overrides" WHERE "name" = 'index_plugin_filter_supportedContexts_idx';`.execute(db); + await sql`DELETE FROM "migration_overrides" WHERE "name" = 'index_plugin_action_supportedContexts_idx';`.execute(db); +} + +export async function down(): Promise { + // not supported +} diff --git a/server/src/schema/tables/asset-exif.table.ts b/server/src/schema/tables/asset-exif.table.ts index ae47ecfb10..d725189e6d 100644 --- a/server/src/schema/tables/asset-exif.table.ts +++ b/server/src/schema/tables/asset-exif.table.ts @@ -111,7 +111,7 @@ export class AssetExifTable { tags!: string[] | null; @UpdateDateColumn({ default: () => 'clock_timestamp()' }) - updatedAt!: Generated; + updatedAt!: Generated; @UpdateIdColumn({ index: true }) updateId!: Generated; diff --git a/server/src/schema/tables/notification.table.ts b/server/src/schema/tables/notification.table.ts index 6bf65808f1..fd1afc35c7 100644 --- a/server/src/schema/tables/notification.table.ts +++ b/server/src/schema/tables/notification.table.ts @@ -41,7 +41,7 @@ export class NotificationTable { type!: Generated; @Column({ type: 'jsonb', nullable: true }) - data!: any | null; + data!: unknown | null; @Column() title!: string; diff --git a/server/src/schema/tables/plugin-method.table.ts b/server/src/schema/tables/plugin-method.table.ts new file mode 100644 index 0000000000..10cd7b449e --- /dev/null +++ b/server/src/schema/tables/plugin-method.table.ts @@ -0,0 +1,35 @@ +import { Column, ForeignKeyColumn, Generated, PrimaryGeneratedColumn, Table, Unique } from '@immich/sql-tools'; +import { JsonSchemaDto } from 'src/dtos/json-schema.dto'; +import { WorkflowType } from 'src/enum'; +import { PluginTable } from 'src/schema/tables/plugin.table'; + +@Unique({ columns: ['pluginId', 'name'] }) +@Table('plugin_method') +export class PluginMethodTable { + @PrimaryGeneratedColumn('uuid') + id!: Generated; + + @ForeignKeyColumn(() => PluginTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) + pluginId!: string; + + @Column() + name!: string; + + @Column() + title!: string; + + @Column() + description!: string; + + @Column({ type: 'character varying', array: true }) + types!: Generated; + + @Column({ type: 'boolean', default: false }) + hostFunctions!: Generated; + + @Column({ type: 'jsonb', nullable: true }) + schema!: JsonSchemaDto | null; + + @Column({ type: 'character varying', default: [], array: true }) + uiHints!: Generated; +} diff --git a/server/src/schema/tables/plugin.table.ts b/server/src/schema/tables/plugin.table.ts index 5f82807f23..99763a4d43 100644 --- a/server/src/schema/tables/plugin.table.ts +++ b/server/src/schema/tables/plugin.table.ts @@ -1,25 +1,29 @@ import { Column, CreateDateColumn, - ForeignKeyColumn, Generated, - Index, PrimaryGeneratedColumn, Table, Timestamp, + Unique, UpdateDateColumn, } from '@immich/sql-tools'; -import { PluginContext } from 'src/enum'; -import type { JSONSchema } from 'src/types/plugin-schema.types'; +@Unique({ columns: ['name', 'version'] }) @Table('plugin') export class PluginTable { @PrimaryGeneratedColumn('uuid') id!: Generated; + @Column({ type: 'boolean', default: true }) + enabled!: Generated; + @Column({ index: true, unique: true }) name!: string; + @Column() + version!: string; + @Column() title!: string; @@ -29,11 +33,8 @@ export class PluginTable { @Column() author!: string; - @Column() - version!: string; - - @Column() - wasmPath!: string; + @Column({ type: 'bytea' }) + wasmBytes!: Buffer; @CreateDateColumn() createdAt!: Generated; @@ -41,55 +42,3 @@ export class PluginTable { @UpdateDateColumn() updatedAt!: Generated; } - -@Index({ columns: ['supportedContexts'], using: 'gin' }) -@Table('plugin_filter') -export class PluginFilterTable { - @PrimaryGeneratedColumn('uuid') - id!: Generated; - - @ForeignKeyColumn(() => PluginTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) - @Column({ index: true }) - pluginId!: string; - - @Column({ index: true, unique: true }) - methodName!: string; - - @Column() - title!: string; - - @Column() - description!: string; - - @Column({ type: 'character varying', array: true }) - supportedContexts!: Generated; - - @Column({ type: 'jsonb', nullable: true }) - schema!: JSONSchema | null; -} - -@Index({ columns: ['supportedContexts'], using: 'gin' }) -@Table('plugin_action') -export class PluginActionTable { - @PrimaryGeneratedColumn('uuid') - id!: Generated; - - @ForeignKeyColumn(() => PluginTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) - @Column({ index: true }) - pluginId!: string; - - @Column({ index: true, unique: true }) - methodName!: string; - - @Column() - title!: string; - - @Column() - description!: string; - - @Column({ type: 'character varying', array: true }) - supportedContexts!: Generated; - - @Column({ type: 'jsonb', nullable: true }) - schema!: JSONSchema | null; -} diff --git a/server/src/schema/tables/workflow-step.table.ts b/server/src/schema/tables/workflow-step.table.ts new file mode 100644 index 0000000000..49cb58c731 --- /dev/null +++ b/server/src/schema/tables/workflow-step.table.ts @@ -0,0 +1,26 @@ +import { WorkflowStepConfig } from '@immich/plugin-sdk'; +import { Column, ForeignKeyColumn, PrimaryGeneratedColumn, Table } from '@immich/sql-tools'; +import { Generated } from 'kysely'; +import { PluginMethodTable } from 'src/schema/tables/plugin-method.table'; +import { WorkflowTable } from 'src/schema/tables/workflow.table'; + +@Table('workflow_step') +export class WorkflowStepTable { + @PrimaryGeneratedColumn('uuid') + id!: Generated; + + @Column({ type: 'boolean', default: true }) + enabled!: boolean; + + @ForeignKeyColumn(() => WorkflowTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) + workflowId!: string; + + @ForeignKeyColumn(() => PluginMethodTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) + pluginMethodId!: string; + + @Column({ type: 'jsonb', nullable: true }) + config!: WorkflowStepConfig | null; + + @Column({ type: 'integer' }) + order!: number; +} diff --git a/server/src/schema/tables/workflow.table.ts b/server/src/schema/tables/workflow.table.ts index 163518e039..8ac89d4b65 100644 --- a/server/src/schema/tables/workflow.table.ts +++ b/server/src/schema/tables/workflow.table.ts @@ -3,17 +3,17 @@ import { CreateDateColumn, ForeignKeyColumn, Generated, - Index, PrimaryGeneratedColumn, Table, Timestamp, + UpdateDateColumn, } from '@immich/sql-tools'; -import { PluginTriggerType } from 'src/enum'; -import { PluginActionTable, PluginFilterTable } from 'src/schema/tables/plugin.table'; +import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; +import { WorkflowTrigger } from 'src/enum'; import { UserTable } from 'src/schema/tables/user.table'; -import type { ActionConfig, FilterConfig } from 'src/types/plugin-schema.types'; @Table('workflow') +@UpdatedAtTrigger('workflow_updatedAt') export class WorkflowTable { @PrimaryGeneratedColumn() id!: Generated; @@ -22,57 +22,23 @@ export class WorkflowTable { ownerId!: string; @Column() - triggerType!: PluginTriggerType; + trigger!: WorkflowTrigger; @Column({ nullable: true }) name!: string | null; - @Column() - description!: string; + @Column({ nullable: true }) + description!: string | null; @CreateDateColumn() createdAt!: Generated; + @UpdateDateColumn() + updatedAt!: Generated; + + @UpdateIdColumn() + updateId!: Generated; + @Column({ type: 'boolean', default: true }) - enabled!: boolean; -} - -@Index({ columns: ['workflowId', 'order'] }) -@Index({ columns: ['pluginFilterId'] }) -@Table('workflow_filter') -export class WorkflowFilterTable { - @PrimaryGeneratedColumn('uuid') - id!: Generated; - - @ForeignKeyColumn(() => WorkflowTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) - workflowId!: Generated; - - @ForeignKeyColumn(() => PluginFilterTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) - pluginFilterId!: string; - - @Column({ type: 'jsonb', nullable: true }) - filterConfig!: FilterConfig | null; - - @Column({ type: 'integer' }) - order!: number; -} - -@Index({ columns: ['workflowId', 'order'] }) -@Index({ columns: ['pluginActionId'] }) -@Table('workflow_action') -export class WorkflowActionTable { - @PrimaryGeneratedColumn('uuid') - id!: Generated; - - @ForeignKeyColumn(() => WorkflowTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) - workflowId!: Generated; - - @ForeignKeyColumn(() => PluginActionTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) - pluginActionId!: string; - - @Column({ type: 'jsonb', nullable: true }) - actionConfig!: ActionConfig | null; - - @Column({ type: 'integer' }) - order!: number; + enabled!: Generated; } diff --git a/server/src/services/base.service.ts b/server/src/services/base.service.ts index d930dd0a31..33534f16de 100644 --- a/server/src/services/base.service.ts +++ b/server/src/services/base.service.ts @@ -58,6 +58,7 @@ import { ViewRepository } from 'src/repositories/view-repository'; import { WebsocketRepository } from 'src/repositories/websocket.repository'; import { WorkflowRepository } from 'src/repositories/workflow.repository'; import { UserTable } from 'src/schema/tables/user.table'; +import { ClassConstructor } from 'src/types'; import { AccessRequest, checkAccess, requireAccess } from 'src/utils/access'; import { getConfig, updateConfig } from 'src/utils/config'; @@ -187,6 +188,66 @@ export class BaseService { ); } + static create(Service: ClassConstructor, ctx: BaseService) { + const service = new Service( + LoggingRepository.create(), + ctx.accessRepository, + ctx.activityRepository, + ctx.albumRepository, + ctx.albumUserRepository, + ctx.apiKeyRepository, + ctx.appRepository, + ctx.assetRepository, + ctx.assetEditRepository, + ctx.assetJobRepository, + ctx.configRepository, + ctx.cronRepository, + ctx.cryptoRepository, + ctx.databaseRepository, + ctx.downloadRepository, + ctx.duplicateRepository, + ctx.emailRepository, + ctx.eventRepository, + ctx.jobRepository, + ctx.libraryRepository, + ctx.machineLearningRepository, + ctx.mapRepository, + ctx.mediaRepository, + ctx.memoryRepository, + ctx.metadataRepository, + ctx.moveRepository, + ctx.notificationRepository, + ctx.oauthRepository, + ctx.ocrRepository, + ctx.partnerRepository, + ctx.personRepository, + ctx.pluginRepository, + ctx.processRepository, + ctx.searchRepository, + ctx.serverInfoRepository, + ctx.sessionRepository, + ctx.sharedLinkRepository, + ctx.sharedLinkAssetRepository, + ctx.stackRepository, + ctx.storageRepository, + ctx.syncRepository, + ctx.syncCheckpointRepository, + ctx.systemMetadataRepository, + ctx.tagRepository, + ctx.telemetryRepository, + ctx.trashRepository, + ctx.userRepository, + ctx.versionRepository, + ctx.viewRepository, + ctx.websocketRepository, + ctx.workflowRepository, + ); + + service.logger.setContext(this.name); + + return service as T; + } + get worker() { return this.configRepository.getWorker(); } diff --git a/server/src/services/index.ts b/server/src/services/index.ts index f4e82b13a4..b733483aa8 100644 --- a/server/src/services/index.ts +++ b/server/src/services/index.ts @@ -44,6 +44,7 @@ import { UserAdminService } from 'src/services/user-admin.service'; import { UserService } from 'src/services/user.service'; import { VersionService } from 'src/services/version.service'; import { ViewService } from 'src/services/view.service'; +import { WorkflowExecutionService } from 'src/services/workflow-execution.service'; import { WorkflowService } from 'src/services/workflow.service'; export const services = [ @@ -93,5 +94,6 @@ export const services = [ UserService, VersionService, ViewService, + WorkflowExecutionService, WorkflowService, ]; diff --git a/server/src/services/plugin-host.functions.ts b/server/src/services/plugin-host.functions.ts deleted file mode 100644 index 50b1052b54..0000000000 --- a/server/src/services/plugin-host.functions.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { CurrentPlugin } from '@extism/extism'; -import { UnauthorizedException } from '@nestjs/common'; -import { Updateable } from 'kysely'; -import { Permission } from 'src/enum'; -import { AccessRepository } from 'src/repositories/access.repository'; -import { AlbumRepository } from 'src/repositories/album.repository'; -import { AssetRepository } from 'src/repositories/asset.repository'; -import { CryptoRepository } from 'src/repositories/crypto.repository'; -import { LoggingRepository } from 'src/repositories/logging.repository'; -import { AssetTable } from 'src/schema/tables/asset.table'; -import { requireAccess } from 'src/utils/access'; - -/** - * Plugin host functions that are exposed to WASM plugins via Extism. - * These functions allow plugins to interact with the Immich system. - */ -export class PluginHostFunctions { - constructor( - private assetRepository: AssetRepository, - private albumRepository: AlbumRepository, - private accessRepository: AccessRepository, - private cryptoRepository: CryptoRepository, - private logger: LoggingRepository, - private pluginJwtSecret: string, - ) {} - - /** - * Creates Extism host function bindings for the plugin. - * These are the functions that WASM plugins can call. - */ - getHostFunctions() { - return { - 'extism:host/user': { - updateAsset: (cp: CurrentPlugin, offs: bigint) => this.handleUpdateAsset(cp, offs), - addAssetToAlbum: (cp: CurrentPlugin, offs: bigint) => this.handleAddAssetToAlbum(cp, offs), - }, - }; - } - - /** - * Host function wrapper for updateAsset. - * Reads the input from the plugin, parses it, and calls the actual update function. - */ - private async handleUpdateAsset(cp: CurrentPlugin, offs: bigint) { - const input = JSON.parse(cp.read(offs)!.text()); - await this.updateAsset(input); - } - - /** - * Host function wrapper for addAssetToAlbum. - * Reads the input from the plugin, parses it, and calls the actual add function. - */ - private async handleAddAssetToAlbum(cp: CurrentPlugin, offs: bigint) { - const input = JSON.parse(cp.read(offs)!.text()); - await this.addAssetToAlbum(input); - } - - /** - * Validates the JWT token and returns the auth context. - */ - private validateToken(authToken: string): { userId: string } { - try { - const auth = this.cryptoRepository.verifyJwt<{ userId: string }>(authToken, this.pluginJwtSecret); - if (!auth.userId) { - throw new UnauthorizedException('Invalid token: missing userId'); - } - return auth; - } catch (error) { - this.logger.error('Token validation failed:', error); - throw new UnauthorizedException('Invalid token'); - } - } - - /** - * Updates an asset with the given properties. - */ - async updateAsset(input: { authToken: string } & Updateable & { id: string }) { - const { authToken, id, ...assetData } = input; - - // Validate token - const auth = this.validateToken(authToken); - - // Check access to the asset - await requireAccess(this.accessRepository, { - auth: { user: { id: auth.userId } } as any, - permission: Permission.AssetUpdate, - ids: [id], - }); - - this.logger.log(`Updating asset ${id} -- ${JSON.stringify(assetData)}`); - await this.assetRepository.update({ id, ...assetData }); - } - - /** - * Adds an asset to an album. - */ - async addAssetToAlbum(input: { authToken: string; assetId: string; albumId: string }) { - const { authToken, assetId, albumId } = input; - - // Validate token - const auth = this.validateToken(authToken); - - // Check access to both the asset and the album - await requireAccess(this.accessRepository, { - auth: { user: { id: auth.userId } } as any, - permission: Permission.AssetRead, - ids: [assetId], - }); - - await requireAccess(this.accessRepository, { - auth: { user: { id: auth.userId } } as any, - permission: Permission.AlbumUpdate, - ids: [albumId], - }); - - this.logger.log(`Adding asset ${assetId} to album ${albumId}`); - await this.albumRepository.addAssetIds(albumId, [assetId]); - return 0; - } -} diff --git a/server/src/services/plugin.service.ts b/server/src/services/plugin.service.ts index 7209a613fe..62edf8438b 100644 --- a/server/src/services/plugin.service.ts +++ b/server/src/services/plugin.service.ts @@ -1,313 +1,34 @@ -import { Plugin as ExtismPlugin, newPlugin } from '@extism/extism'; import { BadRequestException, Injectable } from '@nestjs/common'; -import { join } from 'node:path'; -import { Asset, WorkflowAction, WorkflowFilter } from 'src/database'; -import { OnEvent, OnJob } from 'src/decorators'; -import { PluginManifestDto, PluginManifestSchema } from 'src/dtos/plugin-manifest.dto'; -import { mapPlugin, PluginResponseDto, PluginTriggerResponseDto } from 'src/dtos/plugin.dto'; -import { JobName, JobStatus, PluginTriggerType, QueueName } from 'src/enum'; -import { pluginTriggers } from 'src/plugins'; -import { ArgOf } from 'src/repositories/event.repository'; +import { + mapMethod, + mapPlugin, + PluginMethodResponseDto, + PluginMethodSearchDto, + PluginResponseDto, + PluginSearchDto, +} from 'src/dtos/plugin.dto'; import { BaseService } from 'src/services/base.service'; -import { PluginHostFunctions } from 'src/services/plugin-host.functions'; -import { IWorkflowJob, JobItem, JobOf, WorkflowData } from 'src/types'; - -interface WorkflowContext { - authToken: string; - asset: Asset; -} - -interface PluginInput { - authToken: string; - config: T; - data: { - asset: Asset; - }; -} +import { isMethodCompatible } from 'src/utils/workflow'; @Injectable() export class PluginService extends BaseService { - private pluginJwtSecret!: string; - private loadedPlugins: Map = new Map(); - private hostFunctions!: PluginHostFunctions; - - @OnEvent({ name: 'AppBootstrap' }) - async onBootstrap() { - this.pluginJwtSecret = this.cryptoRepository.randomBytesAsText(32); - - await this.loadPluginsFromManifests(); - - this.hostFunctions = new PluginHostFunctions( - this.assetRepository, - this.albumRepository, - this.accessRepository, - this.cryptoRepository, - this.logger, - this.pluginJwtSecret, - ); - - await this.loadPlugins(); - } - - getTriggers(): PluginTriggerResponseDto[] { - return pluginTriggers; - } - - // - // CRUD operations for plugins - // - async getAll(): Promise { - const plugins = await this.pluginRepository.getAllPlugins(); + async search(dto: PluginSearchDto): Promise { + const plugins = await this.pluginRepository.search(dto); return plugins.map((plugin) => mapPlugin(plugin)); } async get(id: string): Promise { - const plugin = await this.pluginRepository.getPlugin(id); + const plugin = await this.pluginRepository.get(id); if (!plugin) { throw new BadRequestException('Plugin not found'); } return mapPlugin(plugin); } - /////////////////////////////////////////// - // Plugin Loader - ////////////////////////////////////////// - async loadPluginsFromManifests(): Promise { - // Load core plugin - const { resourcePaths, plugins } = this.configRepository.getEnv(); - const coreManifestPath = `${resourcePaths.corePlugin}/manifest.json`; - - const coreManifest = await this.readAndValidateManifest(coreManifestPath); - await this.loadPluginToDatabase(coreManifest, resourcePaths.corePlugin); - - this.logger.log(`Successfully processed core plugin: ${coreManifest.name} (version ${coreManifest.version})`); - - // Load external plugins - if (plugins.external.allow && plugins.external.installFolder) { - await this.loadExternalPlugins(plugins.external.installFolder); - } - } - - private async loadExternalPlugins(installFolder: string): Promise { - try { - const entries = await this.pluginRepository.readDirectory(installFolder); - - for (const entry of entries) { - if (!entry.isDirectory()) { - continue; - } - - const pluginFolder = join(installFolder, entry.name); - const manifestPath = join(pluginFolder, 'manifest.json'); - try { - const manifest = await this.readAndValidateManifest(manifestPath); - await this.loadPluginToDatabase(manifest, pluginFolder); - - this.logger.log(`Successfully processed external plugin: ${manifest.name} (version ${manifest.version})`); - } catch (error) { - this.logger.warn(`Failed to load external plugin from ${manifestPath}:`, error); - } - } - } catch (error) { - this.logger.error(`Failed to scan external plugins folder ${installFolder}:`, error); - } - } - - private async loadPluginToDatabase(manifest: PluginManifestDto, basePath: string): Promise { - const currentPlugin = await this.pluginRepository.getPluginByName(manifest.name); - if (currentPlugin != null && currentPlugin.version === manifest.version) { - this.logger.log(`Plugin ${manifest.name} is up to date (version ${manifest.version}). Skipping`); - return; - } - - const { plugin, filters, actions } = await this.pluginRepository.loadPlugin(manifest, basePath); - - this.logger.log(`Upserted plugin: ${plugin.name} (ID: ${plugin.id}, version: ${plugin.version})`); - - for (const filter of filters) { - this.logger.log(`Upserted plugin filter: ${filter.methodName} (ID: ${filter.id})`); - } - - for (const action of actions) { - this.logger.log(`Upserted plugin action: ${action.methodName} (ID: ${action.id})`); - } - } - - private async readAndValidateManifest(manifestPath: string): Promise { - const content = await this.storageRepository.readTextFile(manifestPath); - const manifestData = JSON.parse(content); - return PluginManifestSchema.parse(manifestData); - } - - /////////////////////////////////////////// - // Plugin Execution - /////////////////////////////////////////// - private async loadPlugins() { - const plugins = await this.pluginRepository.getAllPlugins(); - for (const plugin of plugins) { - try { - this.logger.debug(`Loading plugin: ${plugin.name} from ${plugin.wasmPath}`); - - const extismPlugin = await newPlugin(plugin.wasmPath, { - useWasi: true, - functions: this.hostFunctions.getHostFunctions(), - }); - - this.loadedPlugins.set(plugin.id, extismPlugin); - this.logger.log(`Successfully loaded plugin: ${plugin.name}`); - } catch (error) { - this.logger.error(`Failed to load plugin ${plugin.name}:`, error); - } - } - } - - @OnEvent({ name: 'AssetCreate' }) - async handleAssetCreate({ asset }: ArgOf<'AssetCreate'>) { - await this.handleTrigger(PluginTriggerType.AssetCreate, { - ownerId: asset.ownerId, - event: { userId: asset.ownerId, asset }, - }); - } - - private async handleTrigger( - triggerType: T, - params: { ownerId: string; event: WorkflowData[T] }, - ): Promise { - const workflows = await this.workflowRepository.getWorkflowByOwnerAndTrigger(params.ownerId, triggerType); - if (workflows.length === 0) { - return; - } - - const jobs: JobItem[] = workflows.map((workflow) => ({ - name: JobName.WorkflowRun, - data: { - id: workflow.id, - type: triggerType, - event: params.event, - } as IWorkflowJob, - })); - - await this.jobRepository.queueAll(jobs); - this.logger.debug(`Queued ${jobs.length} workflow execution jobs for trigger ${triggerType}`); - } - - @OnJob({ name: JobName.WorkflowRun, queue: QueueName.Workflow }) - async handleWorkflowRun({ id: workflowId, type, event }: JobOf): Promise { - try { - const workflow = await this.workflowRepository.getWorkflow(workflowId); - if (!workflow) { - this.logger.error(`Workflow ${workflowId} not found`); - return JobStatus.Failed; - } - - const workflowFilters = await this.workflowRepository.getFilters(workflowId); - const workflowActions = await this.workflowRepository.getActions(workflowId); - - switch (type) { - case PluginTriggerType.AssetCreate: { - const data = event as WorkflowData[PluginTriggerType.AssetCreate]; - const asset = data.asset; - - const authToken = this.cryptoRepository.signJwt({ userId: data.userId }, this.pluginJwtSecret); - - const context = { - authToken, - asset, - }; - - const filtersPassed = await this.executeFilters(workflowFilters, context); - if (!filtersPassed) { - return JobStatus.Skipped; - } - - await this.executeActions(workflowActions, context); - this.logger.debug(`Workflow ${workflowId} executed successfully`); - return JobStatus.Success; - } - - case PluginTriggerType.PersonRecognized: { - this.logger.error('unimplemented'); - return JobStatus.Skipped; - } - - default: { - this.logger.error(`Unknown workflow trigger type: ${type}`); - return JobStatus.Failed; - } - } - } catch (error) { - this.logger.error(`Error executing workflow ${workflowId}:`, error); - return JobStatus.Failed; - } - } - - private async executeFilters(workflowFilters: WorkflowFilter[], context: WorkflowContext): Promise { - for (const workflowFilter of workflowFilters) { - const filter = await this.pluginRepository.getFilter(workflowFilter.pluginFilterId); - if (!filter) { - this.logger.error(`Filter ${workflowFilter.pluginFilterId} not found`); - return false; - } - - const pluginInstance = this.loadedPlugins.get(filter.pluginId); - if (!pluginInstance) { - this.logger.error(`Plugin ${filter.pluginId} not loaded`); - return false; - } - - const filterInput: PluginInput = { - authToken: context.authToken, - config: workflowFilter.filterConfig, - data: { - asset: context.asset, - }, - }; - - this.logger.debug(`Calling filter ${filter.methodName} with input: ${JSON.stringify(filterInput)}`); - - const filterResult = await pluginInstance.call( - filter.methodName, - new TextEncoder().encode(JSON.stringify(filterInput)), - ); - - if (!filterResult) { - this.logger.error(`Filter ${filter.methodName} returned null`); - return false; - } - - const result = JSON.parse(filterResult.text()); - if (result.passed === false) { - this.logger.debug(`Filter ${filter.methodName} returned false, stopping workflow execution`); - return false; - } - } - - return true; - } - - private async executeActions(workflowActions: WorkflowAction[], context: WorkflowContext): Promise { - for (const workflowAction of workflowActions) { - const action = await this.pluginRepository.getAction(workflowAction.pluginActionId); - if (!action) { - throw new Error(`Action ${workflowAction.pluginActionId} not found`); - } - - const pluginInstance = this.loadedPlugins.get(action.pluginId); - if (!pluginInstance) { - throw new Error(`Plugin ${action.pluginId} not loaded`); - } - - const actionInput: PluginInput = { - authToken: context.authToken, - config: workflowAction.actionConfig, - data: { - asset: context.asset, - }, - }; - - this.logger.debug(`Calling action ${action.methodName} with input: ${JSON.stringify(actionInput)}`); - - await pluginInstance.call(action.methodName, JSON.stringify(actionInput)); - } + async searchMethods(dto: PluginMethodSearchDto): Promise { + const methods = await this.pluginRepository.searchMethods(dto); + return methods + .filter((method) => !dto.trigger || isMethodCompatible(method, dto.trigger)) + .map((method) => mapMethod(method)); } } diff --git a/server/src/services/workflow-execution.service.ts b/server/src/services/workflow-execution.service.ts new file mode 100644 index 0000000000..b2e106a251 --- /dev/null +++ b/server/src/services/workflow-execution.service.ts @@ -0,0 +1,344 @@ +import { CurrentPlugin } from '@extism/extism'; +import { WorkflowChanges, WorkflowEventData, WorkflowEventPayload, WorkflowResponse } from '@immich/plugin-sdk'; +import { HttpException, UnauthorizedException } from '@nestjs/common'; +import _ from 'lodash'; +import { join } from 'node:path'; +import { OnEvent, OnJob } from 'src/decorators'; +import { AlbumsAddAssetsDto } from 'src/dtos/album.dto'; +import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; +import { AuthDto } from 'src/dtos/auth.dto'; +import { PluginManifestDto } from 'src/dtos/plugin-manifest.dto'; +import { + BootstrapEventPriority, + DatabaseLock, + ImmichWorker, + JobName, + JobStatus, + QueueName, + WorkflowTrigger, + WorkflowType, +} from 'src/enum'; +import { ArgOf } from 'src/repositories/event.repository'; +import { AlbumService } from 'src/services/album.service'; +import { BaseService } from 'src/services/base.service'; +import { JobOf } from 'src/types'; + +const dummy = () => { + throw new Error( + `Calling host functions is not allowed without setting methods[].hostFunctions=true in the plugin manifest`, + ); +}; + +type ExecuteOptions = { + read: (type: T) => Promise<{ authUserId: string; data: WorkflowEventData }>; + write: (changes: WorkflowChanges) => Promise; +}; + +export class WorkflowExecutionService extends BaseService { + private jwtSecret!: string; + + @OnEvent({ name: 'AppBootstrap', priority: BootstrapEventPriority.PluginSync, workers: [ImmichWorker.Microservices] }) + async onPluginSync() { + await this.databaseRepository.withLock(DatabaseLock.PluginImport, async () => { + // TODO avoid importing plugins in each worker + // Can this use system metadata similar to geocoding? + + const { resourcePaths, plugins } = this.configRepository.getEnv(); + await this.importFolder(resourcePaths.corePlugin, { force: true }); + + if (plugins.external.allow && plugins.external.installFolder) { + await this.importFolders(plugins.external.installFolder); + } + }); + } + + @OnEvent({ name: 'AppBootstrap', priority: BootstrapEventPriority.PluginLoad, workers: [ImmichWorker.Microservices] }) + async onPluginLoad() { + this.jwtSecret = this.cryptoRepository.randomBytesAsText(32); + + const albumService = BaseService.create(AlbumService, this); + + const albumAddAssets = this.wrap<[id: string, dto: BulkIdsDto]>((authDto, args) => + albumService.addAssets(authDto, ...args), + ); + + const addAssetsToAlbums = this.wrap<[dto: AlbumsAddAssetsDto]>((authDto, args) => + albumService.addAssetsToAlbums(authDto, ...args), + ); + + const functions = { + albumAddAssets, + addAssetsToAlbums, + }; + + const stubs = { + albumAddAssets: dummy, + addAssetsToAlbums: dummy, + }; + + const plugins = await this.pluginRepository.getForLoad(); + for (const { id, name, version, wasmBytes, methods } of plugins) { + const method = methods.some(({ hostFunctions }) => !hostFunctions); + if (method) { + const label = `${name}@${version}`; + const key = this.getPluginKey({ id, hostFunctions: false }); + try { + await this.pluginRepository.load({ key, label, wasmBytes }, { runInWorker: false, functions: stubs }); + this.logger.log(`Loaded plugin: ${label}`); + } catch (error) { + this.logger.error(`Unable to load plugin ${label} (${id})`, error); + } + } + + const methodWithFunction = methods.some(({ hostFunctions }) => hostFunctions); + if (methodWithFunction) { + const label = `${name}@${version}/worker`; + const key = this.getPluginKey({ id, hostFunctions: true }); + try { + await this.pluginRepository.load({ key, label, wasmBytes }, { runInWorker: true, functions }); + this.logger.log(`Loaded plugin with host functions: ${label}`); + } catch (error) { + this.logger.error(`Unable to load plugin with host functions ${label} (${id})`, error); + } + } + } + } + + private getPluginKey({ id, hostFunctions }: { id: string; hostFunctions: boolean }) { + return id + (hostFunctions ? '/worker' : ''); + } + + private wrap(fn: (authDto: AuthDto, args: T) => Promise) { + return async (plugin: CurrentPlugin, offset: bigint) => { + try { + const handle = plugin.read(offset); + if (!handle) { + return plugin.store( + JSON.stringify({ success: false, status: 400, message: 'Called host function without input' }), + ); + } + + const { authToken, args } = handle.json() as { authToken: string; args: T }; + if (!authToken) { + throw new Error('authToken is required'); + } + + const authDto = this.validate(authToken); + const response = await fn(authDto, args); + + return plugin.store(JSON.stringify({ success: true, response })); + } catch (error: Error | any) { + if (error instanceof HttpException) { + this.logger.error(`Plugin host exception: ${error}`); + return plugin.store( + JSON.stringify({ success: false, status: error.getStatus(), message: error.getResponse() }), + ); + } + + this.logger.error(`Plugin host exception: ${error}`, error?.stack); + + return plugin.store( + JSON.stringify({ + success: false, + status: 500, + message: `Internal server error: ${error}`, + }), + ); + } + }; + } + + private async importFolders(installFolder: string): Promise { + try { + const entries = await this.storageRepository.readdirWithTypes(installFolder); + for (const entry of entries) { + if (!entry.isDirectory()) { + continue; + } + + await this.importFolder(join(installFolder, entry.name)); + } + } catch (error) { + this.logger.error(`Failed to import plugins folder ${installFolder}:`, error); + } + } + + private async importFolder(folder: string, options?: { force?: boolean }) { + try { + const manifestPath = join(folder, 'manifest.json'); + const dto = await this.storageRepository.readJsonFile(manifestPath); + const result = PluginManifestDto.schema.safeParse(dto); + if (!result.success) { + const issues = result.error.issues.map((issue) => ` - [${issue.path.join('.')}] ${issue.message}`).join('\n'); + this.logger.warn(`Invalid plugin manifest at ${manifestPath}:\n${issues}`); + return; + } + const manifest = result.data; + + const existing = await this.pluginRepository.getByName(manifest.name); + if (existing && existing.version === manifest.version && options?.force !== true) { + return; + } + + const wasmPath = `${folder}/${manifest.wasmPath}`; + const wasmBytes = await this.storageRepository.readFile(wasmPath); + + const plugin = await this.pluginRepository.upsert( + { + enabled: true, + name: manifest.name, + title: manifest.title, + description: manifest.description, + author: manifest.author, + version: manifest.version, + wasmBytes, + }, + manifest.methods, + ); + + if (existing) { + this.logger.log( + `Upgraded plugin ${manifest.name} (${plugin.methods.length} methods) from ${existing.version} to ${manifest.version} `, + ); + } else { + this.logger.log( + `Imported plugin ${manifest.name}@${manifest.version} (${plugin.methods.length} methods) from ${folder}`, + ); + } + + return manifest; + } catch { + this.logger.warn(`Failed to import plugin from ${folder}:`); + } + } + + private validate(authToken: string): AuthDto { + try { + const jwt = this.cryptoRepository.verifyJwt<{ userId: string }>(authToken, this.jwtSecret); + if (!jwt.userId) { + throw new UnauthorizedException('Invalid token: missing userId'); + } + + return { + user: { + id: jwt.userId, + }, + } as AuthDto; + } catch (error) { + this.logger.error('Token validation failed:', error); + throw new UnauthorizedException('Invalid token'); + } + } + + private sign(userId: string) { + return this.cryptoRepository.signJwt({ userId }, this.jwtSecret); + } + + @OnEvent({ name: 'AssetCreate' }) + async onAssetCreate({ asset }: ArgOf<'AssetCreate'>) { + const dto = { ownerId: asset.ownerId, trigger: WorkflowTrigger.AssetCreate }; + const items = await this.workflowRepository.search(dto); + await this.jobRepository.queueAll( + items.map((workflow) => ({ + name: JobName.WorkflowAssetCreate, + data: { workflowId: workflow.id, assetId: asset.id }, + })), + ); + } + + @OnJob({ name: JobName.WorkflowAssetCreate, queue: QueueName.Workflow }) + handleAssetCreate({ workflowId, assetId }: JobOf) { + return this.execute(workflowId, (type) => { + switch (type) { + case WorkflowType.AssetV1: { + return { + read: async () => { + const asset = await this.workflowRepository.getForAssetV1(assetId); + return { + data: { asset } as any, + authUserId: asset.ownerId, + }; + }, + write: async (changes) => { + if (changes.asset) { + await this.assetRepository.update({ + id: assetId, + ..._.omitBy( + { + isFavorite: changes.asset?.isFavorite, + visibility: changes.asset?.visibility, + }, + _.isUndefined, + ), + }); + } + }, + } satisfies ExecuteOptions; + } + } + }); + } + + private async execute( + workflowId: string, + getHandler: (type: T) => ExecuteOptions | undefined, + ) { + const workflow = await this.workflowRepository.getForWorkflowRun(workflowId); + if (!workflow) { + return; + } + + // TODO infer from steps + const type = 'AssetV1' as T; + const handler = getHandler(type); + if (!handler) { + this.logger.error(`Misconfigured workflow ${workflowId}: no handler for type ${type}`); + return; + } + + try { + const { read, write } = handler; + const readResult = await read(type); + let data = readResult.data; + for (const step of workflow.steps) { + const payload: WorkflowEventPayload = { + trigger: workflow.trigger, + type, + config: step.config ?? {}, + workflow: { + id: workflowId, + authToken: this.sign(readResult.authUserId), + stepId: step.id, + }, + data, + }; + + if (step.methodName.startsWith('noop')) { + continue; + } + + const result = await this.pluginRepository.callMethod>( + { + pluginKey: this.getPluginKey({ id: step.pluginId, hostFunctions: step.hostFunctions }), + methodName: step.methodName, + }, + payload, + ); + if (result?.changes) { + await write(result.changes); + ({ data } = await read(type)); + } + + const shouldContinue = result?.workflow?.continue ?? true; + if (!shouldContinue) { + break; + } + } + + this.logger.debug(`Workflow ${workflowId} executed successfully`); + } catch (error) { + this.logger.error(`Error executing workflow ${workflowId}:`, error); + return JobStatus.Failed; + } + } +} diff --git a/server/src/services/workflow.service.ts b/server/src/services/workflow.service.ts index 1a65182b1f..0a62a60887 100644 --- a/server/src/services/workflow.service.ts +++ b/server/src/services/workflow.service.ts @@ -1,159 +1,114 @@ +import { WorkflowStepConfig } from '@immich/plugin-sdk'; import { BadRequestException, Injectable } from '@nestjs/common'; -import { Workflow } from 'src/database'; import { AuthDto } from 'src/dtos/auth.dto'; import { - mapWorkflowAction, - mapWorkflowFilter, + mapWorkflow, + mapWorkflowShare, WorkflowCreateDto, WorkflowResponseDto, + WorkflowSearchDto, + WorkflowShareResponseDto, + WorkflowTriggerResponseDto, WorkflowUpdateDto, } from 'src/dtos/workflow.dto'; -import { Permission, PluginContext, PluginTriggerType } from 'src/enum'; -import { pluginTriggers } from 'src/plugins'; - +import { Permission, WorkflowTrigger } from 'src/enum'; +import { PluginMethodSearchResponse } from 'src/repositories/plugin.repository'; import { BaseService } from 'src/services/base.service'; +import { getWorkflowTriggers, isMethodCompatible, resolveMethod } from 'src/utils/workflow'; @Injectable() export class WorkflowService extends BaseService { - async create(auth: AuthDto, dto: WorkflowCreateDto): Promise { - const context = this.getContextForTrigger(dto.triggerType); - - const filterInserts = await this.validateAndMapFilters(dto.filters, context); - const actionInserts = await this.validateAndMapActions(dto.actions, context); - - const workflow = await this.workflowRepository.createWorkflow( - { - ownerId: auth.user.id, - triggerType: dto.triggerType, - name: dto.name, - description: dto.description || '', - enabled: dto.enabled ?? true, - }, - filterInserts, - actionInserts, - ); - - return this.mapWorkflow(workflow); + getTriggers(): WorkflowTriggerResponseDto[] { + return getWorkflowTriggers(); } - async getAll(auth: AuthDto): Promise { - const workflows = await this.workflowRepository.getWorkflowsByOwner(auth.user.id); - - return Promise.all(workflows.map((workflow) => this.mapWorkflow(workflow))); + async search(auth: AuthDto, dto: WorkflowSearchDto): Promise { + const workflows = await this.workflowRepository.search({ ...dto, ownerId: auth.user.id }); + return workflows.map((workflow) => mapWorkflow(workflow)); } async get(auth: AuthDto, id: string): Promise { await this.requireAccess({ auth, permission: Permission.WorkflowRead, ids: [id] }); const workflow = await this.findOrFail(id); - return this.mapWorkflow(workflow); + return mapWorkflow(workflow); + } + + async share(auth: AuthDto, id: string): Promise { + await this.requireAccess({ auth, permission: Permission.WorkflowRead, ids: [id] }); + const workflow = await this.findOrFail(id); + return mapWorkflowShare(workflow); + } + + async create(auth: AuthDto, dto: WorkflowCreateDto): Promise { + const { steps: stepsDto, ...workflowDto } = dto; + const steps = await this.resolveAndValidateSteps(stepsDto ?? [], workflowDto.trigger); + + const workflow = await this.workflowRepository.create( + { + ...workflowDto, + ownerId: auth.user.id, + }, + steps.map((step) => ({ + enabled: step.enabled ?? true, + config: step.config as WorkflowStepConfig, + pluginMethodId: step.pluginMethod.id, + })), + ); + + return mapWorkflow({ ...workflow, steps: [] }); } async update(auth: AuthDto, id: string, dto: WorkflowUpdateDto): Promise { await this.requireAccess({ auth, permission: Permission.WorkflowUpdate, ids: [id] }); - if (Object.values(dto).filter((prop) => prop !== undefined).length === 0) { - throw new BadRequestException('No fields to update'); - } - - const workflow = await this.findOrFail(id); - const context = this.getContextForTrigger(dto.triggerType ?? workflow.triggerType); - - const { filters, actions, ...workflowUpdate } = dto; - const filterInserts = filters && (await this.validateAndMapFilters(filters, context)); - const actionInserts = actions && (await this.validateAndMapActions(actions, context)); - - const updatedWorkflow = await this.workflowRepository.updateWorkflow( + const { steps: stepsDto, ...workflowDto } = dto; + const current = await this.findOrFail(id); + const steps = stepsDto ? await this.resolveAndValidateSteps(stepsDto, dto.trigger ?? current.trigger) : undefined; + const workflow = await this.workflowRepository.update( id, - workflowUpdate, - filterInserts, - actionInserts, + workflowDto, + steps?.map((step) => ({ + enabled: step.enabled ?? true, + config: step.config as WorkflowStepConfig, + pluginMethodId: step.pluginMethod.id, + })), ); - return this.mapWorkflow(updatedWorkflow); + return mapWorkflow(workflow); } async delete(auth: AuthDto, id: string): Promise { await this.requireAccess({ auth, permission: Permission.WorkflowDelete, ids: [id] }); - await this.workflowRepository.deleteWorkflow(id); + await this.workflowRepository.delete(id); } - private async validateAndMapFilters( - filters: Array<{ pluginFilterId: string; filterConfig?: any }>, - requiredContext: PluginContext, - ) { - for (const dto of filters) { - const filter = await this.pluginRepository.getFilter(dto.pluginFilterId); - if (!filter) { - throw new BadRequestException(`Invalid filter ID: ${dto.pluginFilterId}`); + private async resolveAndValidateSteps(steps: T[], trigger: WorkflowTrigger) { + const methods = await this.pluginRepository.getForValidation(); + const results: Array = []; + + for (const step of steps) { + const pluginMethod = resolveMethod(methods, step.method); + if (!pluginMethod) { + throw new BadRequestException(`Unknown method ${step.method}`); } - if (!filter.supportedContexts.includes(requiredContext)) { - throw new BadRequestException( - `Filter "${filter.title}" does not support ${requiredContext} context. Supported contexts: ${filter.supportedContexts.join(', ')}`, - ); + if (!isMethodCompatible(pluginMethod, trigger)) { + throw new BadRequestException(`Method "${step.method}" is incompatible with workflow trigger: "${trigger}"`); } + + results.push({ ...step, pluginMethod }); } - return filters.map((dto, index) => ({ - pluginFilterId: dto.pluginFilterId, - filterConfig: dto.filterConfig || null, - order: index, - })); - } + // TODO make sure all steps can use a common WorkflowType - private async validateAndMapActions( - actions: Array<{ pluginActionId: string; actionConfig?: any }>, - requiredContext: PluginContext, - ) { - for (const dto of actions) { - const action = await this.pluginRepository.getAction(dto.pluginActionId); - if (!action) { - throw new BadRequestException(`Invalid action ID: ${dto.pluginActionId}`); - } - if (!action.supportedContexts.includes(requiredContext)) { - throw new BadRequestException( - `Action "${action.title}" does not support ${requiredContext} context. Supported contexts: ${action.supportedContexts.join(', ')}`, - ); - } - } - - return actions.map((dto, index) => ({ - pluginActionId: dto.pluginActionId, - actionConfig: dto.actionConfig || null, - order: index, - })); - } - - private getContextForTrigger(type: PluginTriggerType) { - const trigger = pluginTriggers.find((t) => t.type === type); - if (!trigger) { - throw new BadRequestException(`Invalid trigger type: ${type}`); - } - return trigger.contextType; + return results; } private async findOrFail(id: string) { - const workflow = await this.workflowRepository.getWorkflow(id); + const workflow = await this.workflowRepository.get(id); if (!workflow) { throw new BadRequestException('Workflow not found'); } return workflow; } - - private async mapWorkflow(workflow: Workflow): Promise { - const filters = await this.workflowRepository.getFilters(workflow.id); - const actions = await this.workflowRepository.getActions(workflow.id); - - return { - id: workflow.id, - ownerId: workflow.ownerId, - triggerType: workflow.triggerType, - name: workflow.name, - description: workflow.description, - createdAt: workflow.createdAt.toISOString(), - enabled: workflow.enabled, - filters: filters.map((f) => mapWorkflowFilter(f)), - actions: actions.map((a) => mapWorkflowAction(a)), - }; - } } diff --git a/server/src/types.ts b/server/src/types.ts index aa6bb820cc..c7dc1f5e18 100644 --- a/server/src/types.ts +++ b/server/src/types.ts @@ -1,7 +1,7 @@ import { ShallowDehydrateObject } from 'kysely'; import { SystemConfig } from 'src/config'; import { VECTOR_EXTENSIONS } from 'src/constants'; -import { Asset, AssetFile } from 'src/database'; +import { AssetFile } from 'src/database'; import { UploadFieldName } from 'src/dtos/asset-media.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { AssetEditActionItem } from 'src/dtos/editing.dto'; @@ -22,7 +22,6 @@ import { ImageFormat, JobName, MemoryType, - PluginTriggerType, QueueName, StorageFolder, SyncEntityType, @@ -30,10 +29,13 @@ import { TranscodeTarget, UserMetadataKey, VideoCodec, + WorkflowTrigger, + WorkflowType, } from 'src/enum'; -export type DeepPartial = - T extends Record +export type DeepPartial = T extends Date + ? T + : T extends Record ? { [K in keyof T]?: DeepPartial } : T extends Array ? DeepPartial[] @@ -288,22 +290,11 @@ export interface INotifyAlbumUpdateJob extends IEntityJob, IDelayedJob { recipientId: string; } -export interface WorkflowData { - [PluginTriggerType.AssetCreate]: { - userId: string; - asset: Asset; - }; - [PluginTriggerType.PersonRecognized]: { - personId: string; - assetId: string; - }; -} - -export interface IWorkflowJob { +export type IWorkflowJob = { id: string; + trigger: WorkflowTrigger; type: T; - event: WorkflowData[T]; -} +}; export interface JobCounts { active: number; @@ -413,7 +404,7 @@ export type JobItem = | { name: JobName.Ocr; data: IEntityJob } // Workflow - | { name: JobName.WorkflowRun; data: IWorkflowJob } + | { name: JobName.WorkflowAssetCreate; data: { workflowId: string; assetId: string } } // Editor | { name: JobName.AssetEditThumbnailGeneration; data: IEntityJob }; @@ -574,3 +565,20 @@ export interface UserMetadata extends Record = T | ShallowDehydrateObject; + +export type JSONSchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'object'; + +export type JSONSchemaProperty = { + type: JSONSchemaType; + description?: string; + default?: any; + enum?: string[]; + array?: boolean; + properties?: Record; + required?: string[]; +}; + +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type +export interface ClassConstructor extends Function { + new (...args: any[]): T; +} diff --git a/server/src/types/plugin-schema.types.ts b/server/src/types/plugin-schema.types.ts deleted file mode 100644 index da1f6da935..0000000000 --- a/server/src/types/plugin-schema.types.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * JSON Schema types for plugin configuration schemas - * Based on JSON Schema Draft 7 - */ - -import z from 'zod'; - -const JSONSchemaTypeSchema = z - .enum(['string', 'number', 'integer', 'boolean', 'object', 'array', 'null']) - .meta({ id: 'PluginJsonSchemaType' }); - -const JSONSchemaPropertySchema = z - .object({ - type: JSONSchemaTypeSchema.optional(), - description: z.string().optional(), - default: z.any().optional(), - enum: z.array(z.string()).optional(), - - get items() { - return JSONSchemaPropertySchema.optional(); - }, - - get properties() { - return z.record(z.string(), JSONSchemaPropertySchema).optional(); - }, - - required: z.array(z.string()).optional(), - - get additionalProperties() { - return z.union([z.boolean(), JSONSchemaPropertySchema]).optional(); - }, - }) - .meta({ id: 'PluginJsonSchemaProperty' }); - -export type JSONSchemaProperty = z.infer; - -export const JSONSchemaSchema = z - .object({ - type: JSONSchemaTypeSchema.optional(), - properties: z.record(z.string(), JSONSchemaPropertySchema).optional(), - required: z.array(z.string()).optional(), - additionalProperties: z.boolean().optional(), - description: z.string().optional(), - }) - .meta({ id: 'PluginJsonSchema' }); -export type JSONSchema = z.infer; - -type ConfigValue = string | number | boolean | null | ConfigValue[] | { [key: string]: ConfigValue }; - -const ConfigValueSchema: z.ZodType = z.any().meta({ id: 'PluginConfigValue' }); - -export const FilterConfigSchema = z.record(z.string(), ConfigValueSchema).meta({ id: 'WorkflowFilterConfig' }); -export type FilterConfig = z.infer; - -export const ActionConfigSchema = z.record(z.string(), ConfigValueSchema).meta({ id: 'WorkflowActionConfig' }); -export type ActionConfig = z.infer; diff --git a/server/src/utils/misc.ts b/server/src/utils/misc.ts index 450563cf7e..37fff07fd9 100644 --- a/server/src/utils/misc.ts +++ b/server/src/utils/misc.ts @@ -1,16 +1,12 @@ import { INestApplication } from '@nestjs/common'; import { + ApiBodyOptions, DocumentBuilder, OpenAPIObject, SwaggerCustomOptions, SwaggerDocumentOptions, SwaggerModule, } from '@nestjs/swagger'; -import { - OperationObject, - ReferenceObject, - SchemaObject, -} from '@nestjs/swagger/dist/interfaces/open-api-spec.interface'; import _ from 'lodash'; import { cleanupOpenApiDoc } from 'nestjs-zod'; import { writeFileSync } from 'node:fs'; @@ -23,6 +19,11 @@ import { extraSyncModels } from 'src/dtos/sync.dto'; import { ApiCustomExtension, ImmichCookie, ImmichHeader, MetadataKey } from 'src/enum'; import { LoggingRepository } from 'src/repositories/logging.repository'; +type OperationObject = NonNullable; +type ReferenceOrSchemaObject = Extract['schema']; +type ReferenceObject = Extract; +type SchemaObject = Exclude; + export class ImmichStartupError extends Error {} export const isStartUpError = (error: unknown): error is ImmichStartupError => error instanceof ImmichStartupError; diff --git a/server/src/utils/workflow.spec.ts b/server/src/utils/workflow.spec.ts new file mode 100644 index 0000000000..86bdd94e5b --- /dev/null +++ b/server/src/utils/workflow.spec.ts @@ -0,0 +1,36 @@ +import { WorkflowTrigger, WorkflowType } from 'src/enum'; +import { isMethodCompatible } from 'src/utils/workflow'; + +const tests: Array<{ trigger: WorkflowTrigger; types: WorkflowType[]; expected: boolean }> = [ + { + trigger: WorkflowTrigger.AssetCreate, + types: [WorkflowType.AssetV1], + expected: true, + }, + { + trigger: WorkflowTrigger.AssetCreate, + types: [WorkflowType.AssetPersonV1], + expected: true, + }, + { + trigger: WorkflowTrigger.PersonRecognized, + types: [WorkflowType.AssetPersonV1], + expected: true, + }, + { + trigger: WorkflowTrigger.PersonRecognized, + types: [WorkflowType.AssetV1], + expected: false, + }, + { + trigger: WorkflowTrigger.PersonRecognized, + types: [WorkflowType.AssetV1, WorkflowType.AssetPersonV1], + expected: true, + }, +]; + +describe(isMethodCompatible.name, () => { + it.each(tests)('should return $expected for trigger $trigger with types $types', ({ trigger, types, expected }) => { + expect(isMethodCompatible({ types }, trigger)).toBe(expected); + }); +}); diff --git a/server/src/utils/workflow.ts b/server/src/utils/workflow.ts new file mode 100644 index 0000000000..5803fca342 --- /dev/null +++ b/server/src/utils/workflow.ts @@ -0,0 +1,68 @@ +import { WorkflowTrigger, WorkflowType } from 'src/enum'; +import { PluginMethodSearchResponse } from 'src/repositories/plugin.repository'; + +export const triggerMap: Record = { + [WorkflowTrigger.AssetCreate]: [WorkflowType.AssetV1], + [WorkflowTrigger.PersonRecognized]: [WorkflowType.AssetPersonV1], +}; + +export const getWorkflowTriggers = () => + Object.entries(triggerMap).map(([trigger, types]) => ({ trigger: trigger as WorkflowTrigger, types })); + +/** some types extend other types and have implied compatibility */ +const inferredMap: Record = { + [WorkflowType.AssetV1]: [], + [WorkflowType.AssetPersonV1]: [WorkflowType.AssetV1], +}; + +const withImpliedItems = (type: WorkflowType): WorkflowType[] => { + const childTypes = inferredMap[type]; + const results = [type]; + for (const child of childTypes) { + results.push(...withImpliedItems(child)); + } + + return results; +}; + +export const isMethodCompatible = (pluginMethod: { types: WorkflowType[] }, trigger: WorkflowTrigger) => { + const validTypes = triggerMap[trigger]; + const pluginCompatibility = pluginMethod.types.map((type) => withImpliedItems(type)); + for (const requested of validTypes) { + for (const pluginCompatibilityGroup of pluginCompatibility) { + if (pluginCompatibilityGroup.includes(requested)) { + return true; + } + } + } + + return false; +}; + +export const resolveMethod = (methods: PluginMethodSearchResponse[], method: string) => { + const result = parseMethodString(method); + if (!result) { + return; + } + + const { pluginName, methodName } = result; + + return methods.find((method) => method.pluginName === pluginName && method.name === methodName); +}; + +export const asMethodString = (method: { pluginName: string; methodName: string }) => { + return `${method.pluginName}#${method.methodName}`; +}; + +const METHOD_REGEX = /^(?[^@#\s]+)(?:@(?[^#\s]*))?#(?[^@#\s]+)$/; +export const parseMethodString = (method: string) => { + const matches = METHOD_REGEX.exec(method); + if (!matches) { + return; + } + + const pluginName = matches.groups?.name; + const version = matches.groups?.version; + const methodName = matches.groups?.method; + return { pluginName, version, methodName }; +}; diff --git a/server/test/medium.factory.ts b/server/test/medium.factory.ts index 8e3372011a..e7915d3f1c 100644 --- a/server/test/medium.factory.ts +++ b/server/test/medium.factory.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unsafe-function-type */ import { Insertable, Kysely } from 'kysely'; import { DateTime } from 'luxon'; import { createHash, randomBytes } from 'node:crypto'; @@ -75,7 +74,7 @@ import { UserTable } from 'src/schema/tables/user.table'; import { BASE_SERVICE_DEPENDENCIES, BaseService } from 'src/services/base.service'; import { MetadataService } from 'src/services/metadata.service'; import { SyncService } from 'src/services/sync.service'; -import { UploadFile } from 'src/types'; +import { ClassConstructor, UploadFile } from 'src/types'; import { mockEnvData } from 'test/repositories/config.repository.mock'; import { newTelemetryRepositoryMock } from 'test/repositories/telemetry.repository.mock'; import { factory, newDate, newEmbedding, newUuid } from 'test/small.factory'; @@ -85,10 +84,6 @@ import { Mocked } from 'vitest'; // eslint-disable-next-line unicorn/prefer-module export const testAssetsDir = resolve(__dirname, '../../e2e/test-assets'); -interface ClassConstructor extends Function { - new (...args: any[]): T; -} - type MediumTestOptions = { mock: ClassConstructor[]; real: ClassConstructor[]; @@ -425,7 +420,6 @@ const newRealRepository = (key: ClassConstructor, db: Kysely): T => { case OcrRepository: case PartnerRepository: case PersonRepository: - case PluginRepository: case SearchRepository: case SessionRepository: case SharedLinkRepository: @@ -458,6 +452,10 @@ const newRealRepository = (key: ClassConstructor, db: Kysely): T => { return new key(LoggingRepository.create()); } + case PluginRepository: { + return new key(db, LoggingRepository.create()); + } + case StorageRepository: { return new key(LoggingRepository.create()); } @@ -489,7 +487,6 @@ const newMockRepository = (key: ClassConstructor) => { case OcrRepository: case PartnerRepository: case PersonRepository: - case PluginRepository: case SessionRepository: case SyncRepository: case SyncCheckpointRepository: diff --git a/server/test/medium/specs/services/plugin.service.spec.ts b/server/test/medium/specs/services/plugin.service.spec.ts index b70e8e8d54..88254db17e 100644 --- a/server/test/medium/specs/services/plugin.service.spec.ts +++ b/server/test/medium/specs/services/plugin.service.spec.ts @@ -1,5 +1,5 @@ import { Kysely } from 'kysely'; -import { PluginContext } from 'src/enum'; +import { WorkflowType } from 'src/enum'; import { AccessRepository } from 'src/repositories/access.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { PluginRepository } from 'src/repositories/plugin.repository'; @@ -9,7 +9,8 @@ import { newMediumService } from 'test/medium.factory'; import { getKyselyDB } from 'test/utils'; let defaultDatabase: Kysely; -let pluginRepo: PluginRepository; + +const wasmBytes = Buffer.from('some-wasm-binary-data'); const setup = (db?: Kysely) => { return newMediumService(PluginService, { @@ -21,7 +22,6 @@ const setup = (db?: Kysely) => { beforeAll(async () => { defaultDatabase = await getKyselyDB(); - pluginRepo = new PluginRepository(defaultDatabase); }); afterEach(async () => { @@ -32,214 +32,195 @@ describe(PluginService.name, () => { describe('getAll', () => { it('should return empty array when no plugins exist', async () => { const { sut } = setup(); - - const plugins = await sut.getAll(); - - expect(plugins).toEqual([]); + await expect(sut.search({})).resolves.toEqual([]); }); - it('should return plugin without filters and actions', async () => { - const { sut } = setup(); + it('should return plugin without methods', async () => { + const { ctx, sut } = setup(); - const result = await pluginRepo.loadPlugin( + const result = await ctx.get(PluginRepository).upsert( { + enabled: true, name: 'test-plugin', title: 'Test Plugin', description: 'A test plugin', author: 'Test Author', version: '1.0.0', - wasm: { path: '/path/to/test.wasm' }, + wasmBytes, }, - '/test/base/path', + [], ); - const plugins = await sut.getAll(); + const plugins = await sut.search({}); expect(plugins).toHaveLength(1); expect(plugins[0]).toMatchObject({ - id: result.plugin.id, + id: result.id, name: 'test-plugin', description: 'A test plugin', author: 'Test Author', version: '1.0.0', - filters: [], - actions: [], + methods: [], }); }); - it('should return plugin with filters and actions', async () => { - const { sut } = setup(); + it('should return plugin with multiple methods', async () => { + const { ctx, sut } = setup(); - const result = await pluginRepo.loadPlugin( + await ctx.get(PluginRepository).upsert( { + enabled: true, name: 'full-plugin', title: 'Full Plugin', - description: 'A plugin with filters and actions', + description: 'A plugin with multiple methods', author: 'Test Author', version: '1.0.0', - wasm: { path: '/path/to/full.wasm' }, - filters: [ - { - methodName: 'test-filter', - title: 'Test Filter', - description: 'A test filter', - supportedContexts: [PluginContext.Asset], - schema: { type: 'object', properties: {} }, - }, - ], - actions: [ - { - methodName: 'test-action', - title: 'Test Action', - description: 'A test action', - supportedContexts: [PluginContext.Asset], - schema: { type: 'object', properties: {} }, - }, - ], + wasmBytes, }, - '/test/base/path', + [ + { + name: 'test-filter', + title: 'Test Filter', + description: 'A test filter', + types: [WorkflowType.AssetV1], + schema: { type: 'object', properties: {} }, + }, + { + name: 'test-action', + title: 'Test Action', + description: 'A test action', + types: [WorkflowType.AssetV1], + schema: { type: 'object', properties: {} }, + }, + ], ); - const plugins = await sut.getAll(); + const plugins = await sut.search({}); expect(plugins).toHaveLength(1); expect(plugins[0]).toMatchObject({ - id: result.plugin.id, name: 'full-plugin', - filters: [ + methods: [ { - id: result.filters[0].id, - pluginId: result.plugin.id, - methodName: 'test-filter', + name: 'test-filter', title: 'Test Filter', description: 'A test filter', - supportedContexts: [PluginContext.Asset], + types: [WorkflowType.AssetV1], schema: { type: 'object', properties: {} }, }, - ], - actions: [ { - id: result.actions[0].id, - pluginId: result.plugin.id, - methodName: 'test-action', + name: 'test-action', title: 'Test Action', description: 'A test action', - supportedContexts: [PluginContext.Asset], + types: [WorkflowType.AssetV1], schema: { type: 'object', properties: {} }, }, ], }); }); - it('should return multiple plugins with their respective filters and actions', async () => { - const { sut } = setup(); + it('should return multiple plugins with their respective methods', async () => { + const { ctx, sut } = setup(); - await pluginRepo.loadPlugin( + await ctx.get(PluginRepository).upsert( { + enabled: true, name: 'plugin-1', title: 'Plugin 1', description: 'First plugin', author: 'Author 1', version: '1.0.0', - wasm: { path: '/path/to/plugin1.wasm' }, - filters: [ - { - methodName: 'filter-1', - title: 'Filter 1', - description: 'Filter for plugin 1', - supportedContexts: [PluginContext.Asset], - schema: undefined, - }, - ], + wasmBytes, }, - '/test/base/path', + [ + { + name: 'filter-1', + title: 'Filter 1', + description: 'Filter for plugin 1', + types: [WorkflowType.AssetV1], + }, + ], ); - await pluginRepo.loadPlugin( + await ctx.get(PluginRepository).upsert( { + enabled: true, name: 'plugin-2', title: 'Plugin 2', description: 'Second plugin', author: 'Author 2', version: '2.0.0', - wasm: { path: '/path/to/plugin2.wasm' }, - actions: [ - { - methodName: 'action-2', - title: 'Action 2', - description: 'Action for plugin 2', - supportedContexts: [PluginContext.Album], - schema: undefined, - }, - ], + wasmBytes, }, - '/test/base/path', + [ + { + name: 'action-2', + title: 'Action 2', + description: 'Action for plugin 2', + types: [WorkflowType.AssetV1], + }, + ], ); - const plugins = await sut.getAll(); + const plugins = await sut.search({}); expect(plugins).toHaveLength(2); expect(plugins[0].name).toBe('plugin-1'); - expect(plugins[0].filters).toHaveLength(1); - expect(plugins[0].actions).toHaveLength(0); + expect(plugins[0].methods).toHaveLength(1); expect(plugins[1].name).toBe('plugin-2'); - expect(plugins[1].filters).toHaveLength(0); - expect(plugins[1].actions).toHaveLength(1); + expect(plugins[1].methods).toHaveLength(1); }); - it('should handle plugin with multiple filters and actions', async () => { - const { sut } = setup(); + it('should handle plugin with multiple methods', async () => { + const { ctx, sut } = setup(); - await pluginRepo.loadPlugin( + await ctx.get(PluginRepository).upsert( { + enabled: true, name: 'multi-plugin', title: 'Multi Plugin', - description: 'Plugin with multiple items', + description: 'Plugin with multiple methods', author: 'Test Author', version: '1.0.0', - wasm: { path: '/path/to/multi.wasm' }, - filters: [ - { - methodName: 'filter-a', - title: 'Filter A', - description: 'First filter', - supportedContexts: [PluginContext.Asset], - schema: undefined, - }, - { - methodName: 'filter-b', - title: 'Filter B', - description: 'Second filter', - supportedContexts: [PluginContext.Album], - schema: undefined, - }, - ], - actions: [ - { - methodName: 'action-x', - title: 'Action X', - description: 'First action', - supportedContexts: [PluginContext.Asset], - schema: undefined, - }, - { - methodName: 'action-y', - title: 'Action Y', - description: 'Second action', - supportedContexts: [PluginContext.Person], - schema: undefined, - }, - ], + wasmBytes, }, - '/test/base/path', + [ + { + name: 'filter-a', + title: 'Filter A', + description: 'First filter', + types: [WorkflowType.AssetV1], + schema: undefined, + }, + { + name: 'filter-b', + title: 'Filter B', + description: 'Second filter', + types: [WorkflowType.AssetV1], + schema: undefined, + }, + { + name: 'action-x', + title: 'Action X', + description: 'First action', + types: [WorkflowType.AssetV1], + schema: undefined, + }, + { + name: 'action-y', + title: 'Action Y', + description: 'Second action', + types: [WorkflowType.AssetV1], + schema: undefined, + }, + ], ); - const plugins = await sut.getAll(); + const plugins = await sut.search({}); expect(plugins).toHaveLength(1); - expect(plugins[0].filters).toHaveLength(2); - expect(plugins[0].actions).toHaveLength(2); + expect(plugins[0].methods).toHaveLength(4); }); }); @@ -250,55 +231,49 @@ describe(PluginService.name, () => { await expect(sut.get('00000000-0000-0000-0000-000000000000')).rejects.toThrow('Plugin not found'); }); - it('should return single plugin with filters and actions', async () => { - const { sut } = setup(); + it('should return single plugin with methods', async () => { + const { ctx, sut } = setup(); - const result = await pluginRepo.loadPlugin( + const result = await ctx.get(PluginRepository).upsert( { + enabled: true, name: 'single-plugin', title: 'Single Plugin', description: 'A single plugin', author: 'Test Author', version: '1.0.0', - wasm: { path: '/path/to/single.wasm' }, - filters: [ - { - methodName: 'single-filter', - title: 'Single Filter', - description: 'A single filter', - supportedContexts: [PluginContext.Asset], - schema: undefined, - }, - ], - actions: [ - { - methodName: 'single-action', - title: 'Single Action', - description: 'A single action', - supportedContexts: [PluginContext.Asset], - schema: undefined, - }, - ], + wasmBytes, }, - '/test/base/path', - ); - - const pluginResult = await sut.get(result.plugin.id); - - expect(pluginResult).toMatchObject({ - id: result.plugin.id, - name: 'single-plugin', - filters: [ + [ { - id: result.filters[0].id, - methodName: 'single-filter', + name: 'single-filter', title: 'Single Filter', + description: 'A single filter', + types: [WorkflowType.AssetV1], + schema: undefined, + }, + { + name: 'single-action', + title: 'Single Action', + description: 'A single action', + types: [WorkflowType.AssetV1], + schema: undefined, }, ], - actions: [ + ); + + const pluginResult = await sut.get(result.id); + + expect(pluginResult).toMatchObject({ + id: result.id, + name: 'single-plugin', + methods: [ { - id: result.actions[0].id, - methodName: 'single-action', + name: 'single-filter', + title: 'Single Filter', + }, + { + name: 'single-action', title: 'Single Action', }, ], diff --git a/server/test/medium/specs/services/workflow.service.spec.ts b/server/test/medium/specs/services/workflow.service.spec.ts index 229737c531..b16b359b85 100644 --- a/server/test/medium/specs/services/workflow.service.spec.ts +++ b/server/test/medium/specs/services/workflow.service.spec.ts @@ -1,5 +1,5 @@ import { Kysely } from 'kysely'; -import { PluginContext, PluginTriggerType } from 'src/enum'; +import { WorkflowTrigger, WorkflowType } from 'src/enum'; import { AccessRepository } from 'src/repositories/access.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { PluginRepository } from 'src/repositories/plugin.repository'; @@ -20,53 +20,48 @@ const setup = (db?: Kysely) => { }); }; +const wasmBytes = Buffer.from('random-wasm-bytes'); + beforeAll(async () => { defaultDatabase = await getKyselyDB(); }); describe(WorkflowService.name, () => { let testPluginId: string; - let testFilterId: string; - let testActionId: string; beforeAll(async () => { - // Create a test plugin with filters and actions once for all tests - const pluginRepo = new PluginRepository(defaultDatabase); - const result = await pluginRepo.loadPlugin( + const { ctx } = setup(); + // Create a test plugin with methods and actions once for all tests + const pluginRepo = ctx.get(PluginRepository); + const result = await pluginRepo.upsert( { + enabled: true, name: 'test-core-plugin', title: 'Test Core Plugin', description: 'A test core plugin for workflow tests', author: 'Test Author', version: '1.0.0', - wasm: { - path: '/test/path.wasm', - }, - filters: [ - { - methodName: 'test-filter', - title: 'Test Filter', - description: 'A test filter', - supportedContexts: [PluginContext.Asset], - schema: undefined, - }, - ], - actions: [ - { - methodName: 'test-action', - title: 'Test Action', - description: 'A test action', - supportedContexts: [PluginContext.Asset], - schema: undefined, - }, - ], + wasmBytes, }, - '/plugins/test-core-plugin', + [ + { + name: 'test-filter', + title: 'Test Filter', + description: 'A test filter', + types: [WorkflowType.AssetV1], + schema: undefined, + }, + { + name: 'test-action', + title: 'Test Action', + description: 'A test action', + types: [WorkflowType.AssetV1], + schema: undefined, + }, + ], ); - testPluginId = result.plugin.id; - testFilterId = result.filters[0].id; - testActionId = result.actions[0].id; + testPluginId = result.id; }); afterAll(async () => { @@ -74,230 +69,27 @@ describe(WorkflowService.name, () => { }); describe('create', () => { - it('should create a workflow without filters or actions', async () => { + it('should create a workflow', async () => { const { sut, ctx } = setup(); const { user } = await ctx.newUser(); const auth = factory.auth({ user }); const workflow = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, + trigger: WorkflowTrigger.AssetCreate, name: 'test-workflow', description: 'A test workflow', enabled: true, - filters: [], - actions: [], }); expect(workflow).toMatchObject({ id: expect.any(String), - ownerId: user.id, - triggerType: PluginTriggerType.AssetCreate, + trigger: WorkflowTrigger.AssetCreate, name: 'test-workflow', description: 'A test workflow', enabled: true, - filters: [], - actions: [], }); }); - - it('should create a workflow with filters and actions', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const workflow = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'test-workflow-with-relations', - description: 'A test workflow with filters and actions', - enabled: true, - filters: [ - { - pluginFilterId: testFilterId, - filterConfig: { key: 'value' }, - }, - ], - actions: [ - { - pluginActionId: testActionId, - actionConfig: { action: 'test' }, - }, - ], - }); - - expect(workflow).toMatchObject({ - id: expect.any(String), - ownerId: user.id, - triggerType: PluginTriggerType.AssetCreate, - name: 'test-workflow-with-relations', - enabled: true, - }); - - expect(workflow.filters).toHaveLength(1); - expect(workflow.filters[0]).toMatchObject({ - id: expect.any(String), - workflowId: workflow.id, - pluginFilterId: testFilterId, - filterConfig: { key: 'value' }, - order: 0, - }); - - expect(workflow.actions).toHaveLength(1); - expect(workflow.actions[0]).toMatchObject({ - id: expect.any(String), - workflowId: workflow.id, - pluginActionId: testActionId, - actionConfig: { action: 'test' }, - order: 0, - }); - }); - - it('should throw error when creating workflow with invalid filter', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - await expect( - sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'invalid-workflow', - description: 'A workflow with invalid filter', - enabled: true, - filters: [{ pluginFilterId: factory.uuid(), filterConfig: { key: 'value' } }], - actions: [], - }), - ).rejects.toThrow('Invalid filter ID'); - }); - - it('should throw error when creating workflow with invalid action', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - await expect( - sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'invalid-workflow', - description: 'A workflow with invalid action', - enabled: true, - filters: [], - actions: [{ pluginActionId: factory.uuid(), actionConfig: { action: 'test' } }], - }), - ).rejects.toThrow('Invalid action ID'); - }); - - it('should throw error when filter does not support trigger context', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - // Create a plugin with a filter that only supports Album context - const pluginRepo = new PluginRepository(defaultDatabase); - const result = await pluginRepo.loadPlugin( - { - name: 'album-only-plugin', - title: 'Album Only Plugin', - description: 'Plugin with album-only filter', - author: 'Test Author', - version: '1.0.0', - wasm: { path: '/test/album-plugin.wasm' }, - filters: [ - { - methodName: 'album-filter', - title: 'Album Filter', - description: 'A filter that only works with albums', - supportedContexts: [PluginContext.Album], - schema: undefined, - }, - ], - }, - '/plugins/test-core-plugin', - ); - - await expect( - sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'invalid-context-workflow', - description: 'A workflow with context mismatch', - enabled: true, - filters: [{ pluginFilterId: result.filters[0].id }], - actions: [], - }), - ).rejects.toThrow('does not support asset context'); - }); - - it('should throw error when action does not support trigger context', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - // Create a plugin with an action that only supports Person context - const pluginRepo = new PluginRepository(defaultDatabase); - const result = await pluginRepo.loadPlugin( - { - name: 'person-only-plugin', - title: 'Person Only Plugin', - description: 'Plugin with person-only action', - author: 'Test Author', - version: '1.0.0', - wasm: { path: '/test/person-plugin.wasm' }, - actions: [ - { - methodName: 'person-action', - title: 'Person Action', - description: 'An action that only works with persons', - supportedContexts: [PluginContext.Person], - schema: undefined, - }, - ], - }, - '/plugins/test-core-plugin', - ); - - await expect( - sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'invalid-context-workflow', - description: 'A workflow with context mismatch', - enabled: true, - filters: [], - actions: [{ pluginActionId: result.actions[0].id }], - }), - ).rejects.toThrow('does not support asset context'); - }); - - it('should create workflow with multiple filters and actions in correct order', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const workflow = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'multi-step-workflow', - description: 'A workflow with multiple filters and actions', - enabled: true, - filters: [ - { pluginFilterId: testFilterId, filterConfig: { step: 1 } }, - { pluginFilterId: testFilterId, filterConfig: { step: 2 } }, - ], - actions: [ - { pluginActionId: testActionId, actionConfig: { step: 1 } }, - { pluginActionId: testActionId, actionConfig: { step: 2 } }, - { pluginActionId: testActionId, actionConfig: { step: 3 } }, - ], - }); - - expect(workflow.filters).toHaveLength(2); - expect(workflow.filters[0].order).toBe(0); - expect(workflow.filters[0].filterConfig).toEqual({ step: 1 }); - expect(workflow.filters[1].order).toBe(1); - expect(workflow.filters[1].filterConfig).toEqual({ step: 2 }); - - expect(workflow.actions).toHaveLength(3); - expect(workflow.actions[0].order).toBe(0); - expect(workflow.actions[1].order).toBe(1); - expect(workflow.actions[2].order).toBe(2); - }); }); describe('getAll', () => { @@ -307,24 +99,20 @@ describe(WorkflowService.name, () => { const auth = factory.auth({ user }); const workflow1 = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, + trigger: WorkflowTrigger.AssetCreate, name: 'workflow-1', description: 'First workflow', enabled: true, - filters: [], - actions: [], }); const workflow2 = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, + trigger: WorkflowTrigger.AssetCreate, name: 'workflow-2', description: 'Second workflow', enabled: false, - filters: [], - actions: [], }); - const workflows = await sut.getAll(auth); + const workflows = await sut.search(auth, {}); expect(workflows).toHaveLength(2); expect(workflows).toEqual( @@ -340,7 +128,7 @@ describe(WorkflowService.name, () => { const { user } = await ctx.newUser(); const auth = factory.auth({ user }); - const workflows = await sut.getAll(auth); + const workflows = await sut.search(auth, {}); expect(workflows).toEqual([]); }); @@ -353,424 +141,15 @@ describe(WorkflowService.name, () => { const auth2 = factory.auth({ user: user2 }); await sut.create(auth1, { - triggerType: PluginTriggerType.AssetCreate, + trigger: WorkflowTrigger.AssetCreate, name: 'user1-workflow', description: 'User 1 workflow', enabled: true, - filters: [], - actions: [], }); - const user2Workflows = await sut.getAll(auth2); + const user2Workflows = await sut.search(auth2, {}); expect(user2Workflows).toEqual([]); }); }); - - describe('get', () => { - it('should return a specific workflow by id', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const created = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'test-workflow', - description: 'A test workflow', - enabled: true, - filters: [{ pluginFilterId: testFilterId, filterConfig: { key: 'value' } }], - actions: [{ pluginActionId: testActionId, actionConfig: { action: 'test' } }], - }); - - const workflow = await sut.get(auth, created.id); - - expect(workflow).toMatchObject({ - id: created.id, - name: 'test-workflow', - description: 'A test workflow', - enabled: true, - }); - expect(workflow.filters).toHaveLength(1); - expect(workflow.actions).toHaveLength(1); - }); - - it('should throw error when workflow does not exist', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - await expect(sut.get(auth, '66da82df-e424-4bf4-b6f3-5d8e71620dae')).rejects.toThrow(); - }); - - it('should throw error when user does not have access to workflow', async () => { - const { sut, ctx } = setup(); - const { user: user1 } = await ctx.newUser(); - const { user: user2 } = await ctx.newUser(); - const auth1 = factory.auth({ user: user1 }); - const auth2 = factory.auth({ user: user2 }); - - const workflow = await sut.create(auth1, { - triggerType: PluginTriggerType.AssetCreate, - name: 'private-workflow', - description: 'Private workflow', - enabled: true, - filters: [], - actions: [], - }); - - await expect(sut.get(auth2, workflow.id)).rejects.toThrow(); - }); - }); - - describe('update', () => { - it('should update workflow basic fields', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const created = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'original-workflow', - description: 'Original description', - enabled: true, - filters: [], - actions: [], - }); - - const updated = await sut.update(auth, created.id, { - name: 'updated-workflow', - description: 'Updated description', - enabled: false, - }); - - expect(updated).toMatchObject({ - id: created.id, - name: 'updated-workflow', - description: 'Updated description', - enabled: false, - }); - }); - - it('should update workflow filters', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const created = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'test-workflow', - description: 'Test', - enabled: true, - filters: [{ pluginFilterId: testFilterId, filterConfig: { old: 'config' } }], - actions: [], - }); - - const updated = await sut.update(auth, created.id, { - filters: [ - { pluginFilterId: testFilterId, filterConfig: { new: 'config' } }, - { pluginFilterId: testFilterId, filterConfig: { second: 'filter' } }, - ], - }); - - expect(updated.filters).toHaveLength(2); - expect(updated.filters[0].filterConfig).toEqual({ new: 'config' }); - expect(updated.filters[1].filterConfig).toEqual({ second: 'filter' }); - }); - - it('should update workflow actions', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const created = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'test-workflow', - description: 'Test', - enabled: true, - filters: [], - actions: [{ pluginActionId: testActionId, actionConfig: { old: 'config' } }], - }); - - const updated = await sut.update(auth, created.id, { - actions: [ - { pluginActionId: testActionId, actionConfig: { new: 'config' } }, - { pluginActionId: testActionId, actionConfig: { second: 'action' } }, - ], - }); - - expect(updated.actions).toHaveLength(2); - expect(updated.actions[0].actionConfig).toEqual({ new: 'config' }); - expect(updated.actions[1].actionConfig).toEqual({ second: 'action' }); - }); - - it('should clear filters when updated with empty array', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const created = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'test-workflow', - description: 'Test', - enabled: true, - filters: [{ pluginFilterId: testFilterId, filterConfig: { key: 'value' } }], - actions: [], - }); - - const updated = await sut.update(auth, created.id, { - filters: [], - }); - - expect(updated.filters).toHaveLength(0); - }); - - it('should throw error when no fields to update', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const created = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'test-workflow', - description: 'Test', - enabled: true, - filters: [], - actions: [], - }); - - await expect(sut.update(auth, created.id, {})).rejects.toThrow('No fields to update'); - }); - - it('should throw error when updating non-existent workflow', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - await expect(sut.update(auth, factory.uuid(), { name: 'updated-name' })).rejects.toThrow(); - }); - - it('should throw error when user does not have access to update workflow', async () => { - const { sut, ctx } = setup(); - const { user: user1 } = await ctx.newUser(); - const { user: user2 } = await ctx.newUser(); - const auth1 = factory.auth({ user: user1 }); - const auth2 = factory.auth({ user: user2 }); - - const workflow = await sut.create(auth1, { - triggerType: PluginTriggerType.AssetCreate, - name: 'private-workflow', - description: 'Private', - enabled: true, - filters: [], - actions: [], - }); - - await expect( - sut.update(auth2, workflow.id, { - name: 'hacked-workflow', - }), - ).rejects.toThrow(); - }); - - it('should throw error when updating with invalid filter', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const created = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'test-workflow', - description: 'Test', - enabled: true, - filters: [], - actions: [], - }); - - await expect( - sut.update(auth, created.id, { - filters: [{ pluginFilterId: factory.uuid(), filterConfig: {} }], - }), - ).rejects.toThrow(); - }); - - it('should throw error when updating with invalid action', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const created = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'test-workflow', - description: 'Test', - enabled: true, - filters: [], - actions: [], - }); - - await expect( - sut.update(auth, created.id, { actions: [{ pluginActionId: factory.uuid(), actionConfig: {} }] }), - ).rejects.toThrow(); - }); - - it('should update trigger type', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const created = await sut.create(auth, { - triggerType: PluginTriggerType.PersonRecognized, - name: 'test-workflow', - description: 'Test', - enabled: true, - filters: [], - actions: [], - }); - - await sut.update(auth, created.id, { - triggerType: PluginTriggerType.AssetCreate, - }); - - const fetched = await sut.get(auth, created.id); - expect(fetched.triggerType).toBe(PluginTriggerType.AssetCreate); - }); - - it('should add filters', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const created = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'test-workflow', - description: 'Test', - enabled: true, - filters: [], - actions: [], - }); - - await sut.update(auth, created.id, { - filters: [ - { pluginFilterId: testFilterId, filterConfig: { first: true } }, - { pluginFilterId: testFilterId, filterConfig: { second: true } }, - ], - }); - - const fetched = await sut.get(auth, created.id); - expect(fetched.filters).toHaveLength(2); - expect(fetched.filters[0].filterConfig).toEqual({ first: true }); - expect(fetched.filters[1].filterConfig).toEqual({ second: true }); - }); - - it('should replace existing filters', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const created = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'test-workflow', - description: 'Test', - enabled: true, - filters: [{ pluginFilterId: testFilterId, filterConfig: { original: true } }], - actions: [], - }); - - await sut.update(auth, created.id, { - filters: [{ pluginFilterId: testFilterId, filterConfig: { replaced: true } }], - }); - - const fetched = await sut.get(auth, created.id); - expect(fetched.filters).toHaveLength(1); - expect(fetched.filters[0].filterConfig).toEqual({ replaced: true }); - }); - - it('should remove existing filters', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const created = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'test-workflow', - description: 'Test', - enabled: true, - filters: [{ pluginFilterId: testFilterId, filterConfig: { toRemove: true } }], - actions: [], - }); - - await sut.update(auth, created.id, { - filters: [], - }); - - const fetched = await sut.get(auth, created.id); - expect(fetched.filters).toHaveLength(0); - }); - }); - - describe('delete', () => { - it('should delete a workflow', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const workflow = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'test-workflow', - description: 'Test', - enabled: true, - filters: [], - actions: [], - }); - - await sut.delete(auth, workflow.id); - - await expect(sut.get(auth, workflow.id)).rejects.toThrow('Not found or no workflow.read access'); - }); - - it('should delete workflow with filters and actions', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const workflow = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'test-workflow', - description: 'Test', - enabled: true, - filters: [{ pluginFilterId: testFilterId, filterConfig: {} }], - actions: [{ pluginActionId: testActionId, actionConfig: {} }], - }); - - await sut.delete(auth, workflow.id); - - await expect(sut.get(auth, workflow.id)).rejects.toThrow('Not found or no workflow.read access'); - }); - - it('should throw error when deleting non-existent workflow', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - await expect(sut.delete(auth, factory.uuid())).rejects.toThrow(); - }); - - it('should throw error when user does not have access to delete workflow', async () => { - const { sut, ctx } = setup(); - const { user: user1 } = await ctx.newUser(); - const { user: user2 } = await ctx.newUser(); - const auth1 = factory.auth({ user: user1 }); - const auth2 = factory.auth({ user: user2 }); - - const workflow = await sut.create(auth1, { - triggerType: PluginTriggerType.AssetCreate, - name: 'private-workflow', - description: 'Private', - enabled: true, - filters: [], - actions: [], - }); - - await expect(sut.delete(auth2, workflow.id)).rejects.toThrow(); - }); - }); }); diff --git a/server/test/medium/specs/workflow/workflow-core-plugin.spec.ts b/server/test/medium/specs/workflow/workflow-core-plugin.spec.ts new file mode 100644 index 0000000000..99f6c67d5c --- /dev/null +++ b/server/test/medium/specs/workflow/workflow-core-plugin.spec.ts @@ -0,0 +1,287 @@ +import { WorkflowStepConfig } from '@immich/plugin-sdk'; +import { Kysely } from 'kysely'; +import { readFileSync } from 'node:fs'; +import { PluginManifestDto } from 'src/dtos/plugin-manifest.dto'; +import { AssetVisibility, LogLevel, WorkflowTrigger } from 'src/enum'; +import { AccessRepository } from 'src/repositories/access.repository'; +import { AlbumRepository } from 'src/repositories/album.repository'; +import { AssetRepository } from 'src/repositories/asset.repository'; +import { ConfigRepository } from 'src/repositories/config.repository'; +import { CryptoRepository } from 'src/repositories/crypto.repository'; +import { DatabaseRepository } from 'src/repositories/database.repository'; +import { LoggingRepository } from 'src/repositories/logging.repository'; +import { PluginRepository } from 'src/repositories/plugin.repository'; +import { StorageRepository } from 'src/repositories/storage.repository'; +import { WorkflowRepository } from 'src/repositories/workflow.repository'; +import { DB } from 'src/schema'; +import { WorkflowExecutionService } from 'src/services/workflow-execution.service'; +import { resolveMethod } from 'src/utils/workflow'; +import { MediumTestContext } from 'test/medium.factory'; +import { mockEnvData } from 'test/repositories/config.repository.mock'; +import { getKyselyDB } from 'test/utils'; + +let initialized = false; + +class WorkflowTestContext extends MediumTestContext { + constructor(database: Kysely) { + super(WorkflowExecutionService, { + database, + real: [ + AccessRepository, + AlbumRepository, + AssetRepository, + CryptoRepository, + DatabaseRepository, + LoggingRepository, + StorageRepository, + PluginRepository, + WorkflowRepository, + ], + mock: [ConfigRepository], + }); + } + + async init() { + if (initialized) { + return; + } + + const mockData = mockEnvData({}); + mockData.resourcePaths.corePlugin = '../packages/plugin-core'; + mockData.plugins.external.allow = false; + this.getMock(ConfigRepository).getEnv.mockReturnValue(mockData); + this.get(LoggingRepository).setLogLevel(LogLevel.Verbose); + + await this.sut.onPluginSync(); + await this.sut.onPluginLoad(); + + initialized = true; + } +} + +type WorkflowTemplate = { + ownerId: string; + trigger: WorkflowTrigger; + steps: WorkflowTemplateStep[]; +}; + +type WorkflowTemplateStep = { + method: string; + config?: WorkflowStepConfig; +}; + +const createWorkflow = async (template: WorkflowTemplate) => { + const workflowRepo = ctx.get(WorkflowRepository); + const pluginRepo = ctx.get(PluginRepository); + + const methods = await pluginRepo.getForValidation(); + const steps = template.steps.map((step) => { + const pluginMethod = resolveMethod(methods, step.method); + if (!pluginMethod) { + throw new Error(`Plugin method not found: ${step.method}`); + } + + return { ...step, pluginMethod }; + }); + + return workflowRepo.create( + { + enabled: true, + name: 'Test workflow', + description: 'A workflow to test the core plugin', + ownerId: template.ownerId, + trigger: template.trigger, + }, + steps.map((step) => ({ + enabled: true, + pluginMethodId: step.pluginMethod.id, + config: step.config, + })), + ); +}; + +let ctx: WorkflowTestContext; + +beforeAll(async () => { + const db = await getKyselyDB(); + ctx = new WorkflowTestContext(db); + await ctx.init(); +}, 30_000); + +describe('core plugin', () => { + describe('validation', () => { + it('should have a valid manifest.json', () => { + const buffer = readFileSync('../packages/plugin-core/manifest.json'); + const result = PluginManifestDto.schema.safeParse(JSON.parse(buffer.toString())); + if (!result.success) { + const issues = + 'error' in result + ? result.error.issues.map((issue) => ` - [${issue.path.join('.')}] ${issue.message}`).join('\n') + : ''; + const message = `Invalid packages/plugin-core/manifest.json:\n${issues}`; + expect(result.success, message).toBe(true); + } + + expect(result.success).toBe(true); + }); + }); + + describe('assetArchive', () => { + it('should archive an asset', async () => { + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + + const workflow = await createWorkflow({ + ownerId: user.id, + trigger: WorkflowTrigger.AssetCreate, + steps: [{ method: 'immich-plugin-core#assetArchive' }], + }); + + await expect(ctx.sut.handleAssetCreate({ workflowId: workflow.id, assetId: asset.id })).resolves.toBeUndefined(); + + await expect(ctx.get(AssetRepository).getById(asset.id)).resolves.toMatchObject({ + visibility: AssetVisibility.Archive, + }); + }); + + it('should unarchive an asset', async () => { + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id, visibility: AssetVisibility.Archive }); + + const workflow = await createWorkflow({ + ownerId: user.id, + trigger: WorkflowTrigger.AssetCreate, + steps: [{ method: 'immich-plugin-core#assetArchive', config: { inverse: true } }], + }); + + await expect(ctx.sut.handleAssetCreate({ workflowId: workflow.id, assetId: asset.id })).resolves.toBeUndefined(); + + await expect(ctx.get(AssetRepository).getById(asset.id)).resolves.toMatchObject({ + visibility: AssetVisibility.Timeline, + }); + }); + }); + + describe('assetLock', () => { + it('should lock an asset', async () => { + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + + const workflow = await createWorkflow({ + ownerId: user.id, + trigger: WorkflowTrigger.AssetCreate, + steps: [{ method: 'immich-plugin-core#assetLock' }], + }); + + await expect(ctx.sut.handleAssetCreate({ workflowId: workflow.id, assetId: asset.id })).resolves.toBeUndefined(); + + await expect(ctx.get(AssetRepository).getById(asset.id)).resolves.toMatchObject({ + visibility: AssetVisibility.Locked, + }); + }); + + it('should unlock an asset', async () => { + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id, visibility: AssetVisibility.Locked }); + + const workflow = await createWorkflow({ + ownerId: user.id, + trigger: WorkflowTrigger.AssetCreate, + steps: [{ method: 'immich-plugin-core#assetLock', config: { inverse: true } }], + }); + + await expect(ctx.sut.handleAssetCreate({ workflowId: workflow.id, assetId: asset.id })).resolves.toBeUndefined(); + + await expect(ctx.get(AssetRepository).getById(asset.id)).resolves.toMatchObject({ + visibility: AssetVisibility.Timeline, + }); + }); + }); + + describe('assetFavorite', () => { + it('should favorite an asset', async () => { + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + + const workflow = await createWorkflow({ + ownerId: user.id, + trigger: WorkflowTrigger.AssetCreate, + steps: [{ method: 'immich-plugin-core#assetFavorite' }], + }); + + await expect(ctx.sut.handleAssetCreate({ workflowId: workflow.id, assetId: asset.id })).resolves.toBeUndefined(); + + await expect(ctx.get(AssetRepository).getById(asset.id)).resolves.toMatchObject({ isFavorite: true }); + }); + + it('should unfavorite an asset', async () => { + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id, isFavorite: true }); + + const workflow = await createWorkflow({ + ownerId: user.id, + trigger: WorkflowTrigger.AssetCreate, + steps: [{ method: 'immich-plugin-core#assetFavorite', config: { inverse: true } }], + }); + + await expect(ctx.sut.handleAssetCreate({ workflowId: workflow.id, assetId: asset.id })).resolves.toBeUndefined(); + + await expect(ctx.get(AssetRepository).getById(asset.id)).resolves.toMatchObject({ isFavorite: false }); + }); + }); + + describe('assetAddToAlbums', () => { + it('should add an asset to an album', async () => { + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id, isFavorite: true }); + const { album } = await ctx.newAlbum({ ownerId: user.id }); + + const workflow = await createWorkflow({ + ownerId: user.id, + trigger: WorkflowTrigger.AssetCreate, + steps: [{ method: 'immich-plugin-core#assetAddToAlbums', config: { albumIds: [album.id] } }], + }); + + await expect(ctx.sut.handleAssetCreate({ workflowId: workflow.id, assetId: asset.id })).resolves.toBeUndefined(); + + await expect(ctx.get(AlbumRepository).getAssetIds(album.id, [asset.id])).resolves.toContain(asset.id); + }); + + it('should add an asset to multiple albums', async () => { + const { user } = await ctx.newUser(); + const [{ asset }, { album: album1 }, { album: album2 }] = await Promise.all([ + ctx.newAsset({ ownerId: user.id, isFavorite: true }), + ctx.newAlbum({ ownerId: user.id }), + ctx.newAlbum({ ownerId: user.id }), + ]); + + const workflow = await createWorkflow({ + ownerId: user.id, + trigger: WorkflowTrigger.AssetCreate, + steps: [{ method: 'immich-plugin-core#assetAddToAlbums', config: { albumIds: [album1.id, album2.id] } }], + }); + + await expect(ctx.sut.handleAssetCreate({ workflowId: workflow.id, assetId: asset.id })).resolves.toBeUndefined(); + + await expect(ctx.get(AlbumRepository).getAssetIds(album1.id, [asset.id])).resolves.toContain(asset.id); + await expect(ctx.get(AlbumRepository).getAssetIds(album2.id, [asset.id])).resolves.toContain(asset.id); + }); + + it('should require album access', async () => { + const { user: user1 } = await ctx.newUser(); + const { user: user2 } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user1.id, isFavorite: true }); + const { album } = await ctx.newAlbum({ ownerId: user2.id }); + + const workflow = await createWorkflow({ + ownerId: user1.id, + trigger: WorkflowTrigger.AssetCreate, + steps: [{ method: 'immich-plugin-core#assetAddToAlbums', config: { albumIds: [album.id] } }], + }); + + await expect(ctx.sut.handleAssetCreate({ workflowId: workflow.id, assetId: asset.id })).resolves.toBeTruthy(); + + await expect(ctx.get(AlbumRepository).getAssetIds(album.id, [asset.id])).resolves.not.toContain(asset.id); + }); + }); +}); diff --git a/server/test/repositories/config.repository.mock.ts b/server/test/repositories/config.repository.mock.ts index a78b01df35..76a4fe5cef 100644 --- a/server/test/repositories/config.repository.mock.ts +++ b/server/test/repositories/config.repository.mock.ts @@ -76,7 +76,7 @@ export const envData: EnvData = { root: '/build/www', indexHtml: '/build/www/index.html', }, - corePlugin: '/build/corePlugin', + corePlugin: '/build/plugins/immich-plugin-core', }, setup: { diff --git a/server/test/repositories/storage.repository.mock.ts b/server/test/repositories/storage.repository.mock.ts index 85c72b6c10..334d7d0d53 100644 --- a/server/test/repositories/storage.repository.mock.ts +++ b/server/test/repositories/storage.repository.mock.ts @@ -53,7 +53,8 @@ export const newStorageRepositoryMock = (): Mocked['readJsonFile'], + readdirWithTypes: vitest.fn(), createFile: vitest.fn(), createWriteStream: vitest.fn(), createOrOverwriteFile: vitest.fn(), diff --git a/server/test/utils.ts b/server/test/utils.ts index 791a457783..75ada7b551 100644 --- a/server/test/utils.ts +++ b/server/test/utils.ts @@ -326,7 +326,7 @@ export const getMocks = () => { oauth: automock(OAuthRepository, { args: [loggerMock] }), partner: automock(PartnerRepository, { strict: false }), person: automock(PersonRepository, { strict: false }), - plugin: automock(PluginRepository, { strict: true }), + plugin: automock(PluginRepository, { strict: true, args: [databaseMock, loggerMock] }), process: automock(ProcessRepository), search: automock(SearchRepository, { strict: false }), // eslint-disable-next-line no-sparse-arrays diff --git a/web/package.json b/web/package.json index 1ff9beb68e..13725c2d56 100644 --- a/web/package.json +++ b/web/package.json @@ -76,7 +76,7 @@ "@sveltejs/adapter-static": "^3.0.8", "@sveltejs/enhanced-img": "^0.10.4", "@sveltejs/kit": "^2.56.1", - "@sveltejs/vite-plugin-svelte": "7.0.0", + "@sveltejs/vite-plugin-svelte": "7.1.2", "@tailwindcss/vite": "^4.2.4", "@testing-library/jest-dom": "^6.4.2", "@testing-library/svelte": "^5.2.8", @@ -103,7 +103,7 @@ "prettier-plugin-sort-json": "^4.1.1", "prettier-plugin-svelte": "^3.3.3", "rollup-plugin-visualizer": "^7.0.0", - "svelte": "5.55.2", + "svelte": "5.55.8", "svelte-check": "^4.4.6", "svelte-eslint-parser": "^1.3.3", "tailwindcss": "^4.2.4", diff --git a/web/src/app.css b/web/src/app.css index 07226be41f..4435b255d2 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -34,7 +34,7 @@ } @utility immich-form-input { - @apply bg-gray-100 ring-1 ring-gray-200 transition outline-none focus-within:ring-1 disabled:cursor-not-allowed dark:bg-gray-800 dark:ring-neutral-900 flex w-full items-center rounded-lg disabled:bg-gray-300 disabled:text-dark dark:disabled:bg-gray-900 dark:disabled:text-gray-200 flex-1 py-2.5 text-base pl-4 pr-4; + @apply bg-gray-100 ring-1 ring-gray-200 transition outline-none focus-within:ring-primary focus-within:ring-1 disabled:cursor-not-allowed dark:bg-gray-800 dark:ring-neutral-900 dark:focus-within:ring-primary flex w-full items-center rounded-lg disabled:bg-gray-300 disabled:text-dark dark:disabled:bg-gray-900 dark:disabled:text-gray-200 flex-1 py-2.5 text-base pl-4 pr-4; } @utility immich-form-label { diff --git a/web/src/lib/components/AdaptiveImage.svelte b/web/src/lib/components/AdaptiveImage.svelte index 39bc4516b7..4f3522887c 100644 --- a/web/src/lib/components/AdaptiveImage.svelte +++ b/web/src/lib/components/AdaptiveImage.svelte @@ -1,3 +1,54 @@ + + {#if action.$if?.() ?? true} +{/snippet} + +
+
+
+ + {#if array} +
+ {#each albumIds as albumId, i (i)} + { + albumIds.splice(i, 1); + albumIds = [...albumIds]; + }} + /> + {/each} + {@render button()} +
+ {:else} + {@const albumId = albumIds[0]} + {#if albumId} + (albumIds = [])} /> + {:else} + {@render button()} + {/if} + {/if} +
diff --git a/web/src/lib/components/SchemaConfiguration.svelte b/web/src/lib/components/SchemaConfiguration.svelte new file mode 100644 index 0000000000..e48f46a402 --- /dev/null +++ b/web/src/lib/components/SchemaConfiguration.svelte @@ -0,0 +1,99 @@ + + + +{#if Object.keys(schema).length === 0} + + +{:else if schema.type === 'object'} + {#if !root} +
+ + {#if description} + {description} + {/if} +
+ {/if} +
+ {#each Object.entries(schema.properties ?? {}) as [childKey, childSchema] (childKey)} + + {/each} +
+{:else if schema.uiHint === 'albumId'} + +{:else if schema.enum && schema.array} + + + +{:else if schema.enum} + + getValue(), setValue} /> + +{:else} + Unknown schema + +{/if} diff --git a/web/src/lib/components/album-page/AlbumCard.svelte b/web/src/lib/components/album-page/AlbumCard.svelte index cdb7e9e27c..e0151deda6 100644 --- a/web/src/lib/components/album-page/AlbumCard.svelte +++ b/web/src/lib/components/album-page/AlbumCard.svelte @@ -23,7 +23,7 @@ showDateRange = false, showItemCount = false, preload = false, - onShowContextMenu = undefined, + onShowContextMenu, }: Props = $props(); const showAlbumContextMenu = (e: MouseEvent) => { diff --git a/web/src/lib/components/album-page/AlbumThumbnail.svelte b/web/src/lib/components/album-page/AlbumThumbnail.svelte new file mode 100644 index 0000000000..037bb78ab9 --- /dev/null +++ b/web/src/lib/components/album-page/AlbumThumbnail.svelte @@ -0,0 +1,50 @@ + + +
+ {#await getAlbumInfo({ ...authManager.params, id: albumId })} + + {:then album} +
+ +

+ {album.albumName} +

+ {#if album.description} +

+ {album.description} +

+ {/if} +
+ +
+
+ {/await} +
diff --git a/web/src/lib/components/asset-viewer/ActivityViewer.svelte b/web/src/lib/components/asset-viewer/ActivityViewer.svelte index cd952fe08b..803495c8bb 100644 --- a/web/src/lib/components/asset-viewer/ActivityViewer.svelte +++ b/web/src/lib/components/asset-viewer/ActivityViewer.svelte @@ -128,7 +128,7 @@ {#if innerHeight}
{#each activityManager.activities as reaction, index (reaction.id)} diff --git a/web/src/lib/components/faces-page/AssignFaceSidePanel.svelte b/web/src/lib/components/faces-page/AssignFaceSidePanel.svelte index 23f17948b1..d7f505add1 100644 --- a/web/src/lib/components/faces-page/AssignFaceSidePanel.svelte +++ b/web/src/lib/components/faces-page/AssignFaceSidePanel.svelte @@ -153,7 +153,7 @@
{:else} -
+
{#each showPeople as person (person.id)} {#if !editedFace.person || person.id !== editedFace.person.id}
diff --git a/web/src/lib/components/shared-components/context-menu/ContextMenu.svelte b/web/src/lib/components/shared-components/context-menu/ContextMenu.svelte index 9d849cfbfc..729a4a4ea9 100644 --- a/web/src/lib/components/shared-components/context-menu/ContextMenu.svelte +++ b/web/src/lib/components/shared-components/context-menu/ContextMenu.svelte @@ -64,7 +64,7 @@
+
{$t('people')}
{#each peopleList as person (person.id)} diff --git a/web/src/lib/components/shared-components/tree/Breadcrumbs.svelte b/web/src/lib/components/shared-components/tree/Breadcrumbs.svelte index 1c58e8feb1..b065888244 100644 --- a/web/src/lib/components/shared-components/tree/Breadcrumbs.svelte +++ b/web/src/lib/components/shared-components/tree/Breadcrumbs.svelte @@ -35,7 +35,7 @@ {/if}
  1. diff --git a/web/src/lib/components/sidebar/Sidebar.svelte b/web/src/lib/components/sidebar/Sidebar.svelte index fa7560e314..54f96a5c09 100644 --- a/web/src/lib/components/sidebar/Sidebar.svelte +++ b/web/src/lib/components/sidebar/Sidebar.svelte @@ -35,7 +35,7 @@ id="sidebar" aria-label={ariaLabel} tabindex="-1" - class="relative z-1 w-0 overflow-x-hidden overflow-y-auto bg-light pt-8 transition-all duration-200 immich-scrollbar sidebar:w-64" + class="relative z-1 w-0 immich-scrollbar overflow-x-hidden overflow-y-auto bg-light pt-8 transition-all duration-200 sidebar:w-64" class:shadow-2xl={isExpanded} class:dark:border-e-immich-dark-gray={isExpanded} class:border-r={isExpanded} diff --git a/web/src/lib/components/timeline/Timeline.svelte b/web/src/lib/components/timeline/Timeline.svelte index 646feee7b9..6bdc16a34a 100644 --- a/web/src/lib/components/timeline/Timeline.svelte +++ b/web/src/lib/components/timeline/Timeline.svelte @@ -616,7 +616,7 @@
    diff --git a/web/src/lib/managers/plugin-manager.svelte.ts b/web/src/lib/managers/plugin-manager.svelte.ts new file mode 100644 index 0000000000..57ca8fded5 --- /dev/null +++ b/web/src/lib/managers/plugin-manager.svelte.ts @@ -0,0 +1,84 @@ +import { + getWorkflowTriggers, + searchPluginMethods, + WorkflowTrigger, + type PluginMethodResponseDto, + type WorkflowTriggerResponseDto, +} from '@immich/sdk'; +import { t } from 'svelte-i18n'; +import { SvelteMap } from 'svelte/reactivity'; +import { get } from 'svelte/store'; +import { authManager } from '$lib/managers/auth-manager.svelte'; +import { eventManager } from '$lib/managers/event-manager.svelte'; + +class PluginManager { + #loading: Promise | undefined; + #methodMap = new SvelteMap(); + #methods = $state([]); + #triggers = $state([]); + + constructor() { + eventManager.on({ + AuthLogout: () => this.clearCache(), + AuthUserLoaded: () => this.initialize(), + }); + + // loaded event might have already happened + if (authManager.authenticated) { + void this.initialize(); + } + } + + get triggers() { + return this.#triggers; + } + + ready() { + return this.initialize(); + } + + getMethod(key: string) { + return this.#methodMap.get(key); + } + + getMethodLabel(key: string) { + const method = this.getMethod(key); + return method?.title ?? get(t)('unknown'); + } + + getTrigger(trigger: WorkflowTrigger) { + const result = this.#triggers.find((t) => t.trigger === trigger); + + if (!result) { + throw new Error(`Unknown trigger type: ${trigger}`); + } + + return result; + } + + private clearCache() { + this.#loading = undefined; + this.#methodMap = new SvelteMap(); + } + + private initialize() { + if (!this.#loading) { + this.#loading = this.load(); + } + + return this.#loading; + } + + private async load() { + const [methods, triggers] = await Promise.all([searchPluginMethods({}), getWorkflowTriggers()]); + + this.#methods = methods; + for (const method of this.#methods) { + this.#methodMap.set(method.key, method); + } + + this.#triggers = triggers; + } +} + +export const pluginManager = new PluginManager(); diff --git a/web/src/lib/modals/AddWorkflowStepModal.svelte b/web/src/lib/modals/AddWorkflowStepModal.svelte deleted file mode 100644 index 980a7fcaae..0000000000 --- a/web/src/lib/modals/AddWorkflowStepModal.svelte +++ /dev/null @@ -1,80 +0,0 @@ - - -{#snippet stepButton(title: string, description?: string, onclick?: () => void)} - -{/snippet} - - onClose()}> - -
    - - {#if filters.length > 0 && (!type || type === 'filter')} -
    - {#each filters as filter (filter.id)} - {@render stepButton(filter.title, filter.description, () => handleSelect('filter', filter))} - {/each} -
    - {/if} - - - {#if actions.length > 0 && (!type || type === 'action')} -
    - {#each actions as action (action.id)} - {@render stepButton(action.title, action.description, () => handleSelect('action', action))} - {/each} -
    - {/if} -
    -
    -
    diff --git a/web/src/lib/modals/AlbumPickerModal.svelte b/web/src/lib/modals/AlbumPickerModal.svelte index 5d0d5692da..3674a21acd 100644 --- a/web/src/lib/modals/AlbumPickerModal.svelte +++ b/web/src/lib/modals/AlbumPickerModal.svelte @@ -21,9 +21,9 @@ let search = $state(''); let selectedRowIndex: number = $state(-1); - interface Props { + type Props = { onClose: (albums?: AlbumResponseDto[]) => void; - } + }; let { onClose }: Props = $props(); @@ -172,7 +172,7 @@ bind:value={search} use:initInput /> -
    +
    {#each albumModalRows as row} {#if row.type === AlbumModalRowType.NEW_ALBUM} diff --git a/web/src/lib/modals/PartnerSelectionModal.svelte b/web/src/lib/modals/PartnerSelectionModal.svelte index fdebcfecb4..d3f7f7dce7 100644 --- a/web/src/lib/modals/PartnerSelectionModal.svelte +++ b/web/src/lib/modals/PartnerSelectionModal.svelte @@ -41,7 +41,7 @@
    {:then _} {#if availableUsers.length > 0} -
    +
    {#each availableUsers as user (user.id)} selectUser(user)} selected={selectedUsers.includes(user)}> diff --git a/web/src/lib/modals/PeoplePickerModal.svelte b/web/src/lib/modals/PeoplePickerModal.svelte index 0b6520105b..10d7ee5033 100644 --- a/web/src/lib/modals/PeoplePickerModal.svelte +++ b/web/src/lib/modals/PeoplePickerModal.svelte @@ -61,7 +61,7 @@
    -
    +
    {#if loading}
    diff --git a/web/src/lib/modals/PluginMethodPicker.svelte b/web/src/lib/modals/PluginMethodPicker.svelte new file mode 100644 index 0000000000..7000fed2cc --- /dev/null +++ b/web/src/lib/modals/PluginMethodPicker.svelte @@ -0,0 +1,41 @@ + + + + {#await searchPluginMethods({ trigger })} +
    + +
    + {:then methods} + + {#each methods as method (method.key)} + onClose(method)}> +
    + {method.title} + {#if method.uiHints.includes('filter')} + {$t('plugin_method_filter_type')} + {/if} + + {#if method.description} + {method.description} + {/if} +
    +
    + {/each} +
    + {/await} +
    diff --git a/web/src/lib/modals/WorkflowAddStepModal.svelte b/web/src/lib/modals/WorkflowAddStepModal.svelte new file mode 100644 index 0000000000..889e175de0 --- /dev/null +++ b/web/src/lib/modals/WorkflowAddStepModal.svelte @@ -0,0 +1,75 @@ + + +{#if method} + +
    +
    + {method.title} + {#if method.description} + {method.description} + {/if} +
    + +
    + + {#if method.schema} +
    +
    + {$t('configuration')} + + + +
    + {/if} + + {#if debug} +
    + {$t('preview')} +