diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index e688c6909..6f6e55b05 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -33,6 +33,9 @@ body: options: # The below comment is used to insert a new version with on-release.yml #NEXT_VERSION# + - 1.46.0 + - 1.45.3 + - 1.45.2 - 1.45.1 - 1.45.0 - 1.44.0 diff --git a/.github/actions/extract-build-artifact/action.yaml b/.github/actions/extract-build-artifact/action.yaml new file mode 100644 index 000000000..c7e83ca83 --- /dev/null +++ b/.github/actions/extract-build-artifact/action.yaml @@ -0,0 +1,64 @@ +name: Extract Build Artifact +description: Extracts artifacts from an existing Docker image to be used for from source installation +inputs: + digest: + description: Digest of Docker image to use + required: true + architecture: + description: Name of architecture, will be used to build artifact content (amd64 or arm64) + required: true + release-tag: + description: Tag of the release to which the artifact will be attached + required: true + repository: + description: Repository to which the release belongs, e.g. owner/repo + required: true + token: + description: GitHub token with permissions to upload release assets + required: true +runs: + using: "composite" + steps: + - name: Prebuilt debian dependencies + uses: ./.github/actions/prebuilt-debian@dev + id: prebuilt-debian + with: + architecture: ${{ inputs.architecture }} + - name: Start docker container for ${{ inputs.architecture }} + run: | + docker run --name homarr \ + -e "SECRET_ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000" \ + --detach --rm ${{ inputs.digest }} + shell: bash + - name: Prepare extraction + run: | + docker exec homarr cp /etc/nginx/templates/nginx.conf /app && \ + mkdir -p ${{ runner.temp }}/extraction/${{ inputs.architecture }} + shell: bash + - name: Extract source from ${{ inputs.architecture }} container (alpine) + run: | + docker exec homarr tar -czf extraction-alpine.tar.gz -C /app . && \ + docker cp homarr:/app/extraction-alpine.tar.gz ${{ runner.temp }}/extraction/${{ inputs.architecture }}/build-alpine-${{ inputs.architecture }}.tar.gz && \ + docker exec homarr rm /app/extraction-alpine.tar.gz + shell: bash + - name: Extract source from ${{ inputs.architecture }} container (debian) + run: | + docker cp ${{ steps.prebuilt-debian.outputs.path }}/. homarr:/app/build && \ + docker cp ${{ steps.prebuilt-debian.outputs.path }}/. homarr:/app/node_modules/better-sqlite3/build/Release && \ + docker exec homarr tar -czf extraction-debian.tar.gz -C /app . && \ + docker cp homarr:/app/extraction-debian.tar.gz ${{ runner.temp }}/extraction/${{ inputs.architecture }}/build-debian-${{ inputs.architecture }}.tar.gz + shell: bash + - name: Stop ${{ inputs.architecture }} container + if: always() + run: docker container remove --force --volumes homarr + shell: bash + - name: Add build archive to release (alpine) + env: + GH_TOKEN: ${{ inputs.token }} + run: gh release upload --repo ${{ inputs.repository }} ${{ inputs.release-tag }} ${{ runner.temp }}/extraction/${{ inputs.architecture }}/build-alpine-${{ inputs.architecture }}.tar.gz --clobber + shell: bash + - name: Add build archive to release (debian) + env: + GH_TOKEN: ${{ inputs.token }} + run: gh release upload --repo ${{ inputs.repository }} ${{ inputs.release-tag }} ${{ runner.temp }}/extraction/${{ inputs.architecture }}/build-debian-${{ inputs.architecture }}.tar.gz --clobber + shell: bash diff --git a/.github/actions/prebuilt-debian/action.yaml b/.github/actions/prebuilt-debian/action.yaml new file mode 100644 index 000000000..9770a7eb1 --- /dev/null +++ b/.github/actions/prebuilt-debian/action.yaml @@ -0,0 +1,42 @@ +name: Prebuilt dependencies for debian +description: Provides prebuilt dependencies for debian based docker images +inputs: + architecture: + description: Name of architecture, will be used to build docker image (e.g. amd64, arm64) + required: true +outputs: + path: + description: Path to extracted prebuilt dependencies + value: ${{ runner.temp }}/prebuilts +runs: + using: "composite" + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build docker image for ${{ inputs.architecture }} + id: build + uses: docker/build-push-action@v6 + with: + push: false + load: true + context: ./deployments/prebuilt-debian + platforms: linux/${{ inputs.architecture }} + tags: prebuilt-debian + - name: Start docker container for ${{ inputs.architecture }} + run: | + docker run --name prebuilt-debian \ + --detach --rm prebuilt-debian + shell: bash + - name: Extract prebuilt dependencies from ${{ inputs.architecture }} container + run: | + mkdir -p ${{ runner.temp }}/prebuilts && \ + docker cp prebuilt-debian:/app/node_modules/better-sqlite3/build/Release/better_sqlite3.node ${{ runner.temp }}/prebuilts/better_sqlite3.node + shell: bash + - name: Stop ${{ inputs.architecture }} container + if: always() + run: docker container remove --force --volumes prebuilt-debian + shell: bash diff --git a/.github/workflows/automatic-approval.yml b/.github/workflows/automatic-approval.yml index 7f212e23b..a8a7b6feb 100644 --- a/.github/workflows/automatic-approval.yml +++ b/.github/workflows/automatic-approval.yml @@ -8,6 +8,7 @@ permissions: {} jobs: approve-automatic-prs: runs-on: ubuntu-latest + timeout-minutes: 2 if: github.actor_id == 158783068 || github.actor_id == 190541745 || github.actor_id == 210161987 # Id of renovate bot and crowdin bot see https://api.github.com/users/homarr-renovate%5Bbot%5D and https://api.github.com/users/homarr-crowdin%5Bbot%5D and https://api.github.com/users/homarr-update-contributors%5Bbot%5D steps: - name: Checkout code diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 2b5e8a3d7..eeaec94f2 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -23,6 +23,7 @@ env: jobs: lint: runs-on: ubuntu-latest + timeout-minutes: 10 steps: - uses: actions/checkout@v6 @@ -38,6 +39,7 @@ jobs: format: runs-on: ubuntu-latest + timeout-minutes: 10 steps: - uses: actions/checkout@v6 @@ -49,6 +51,7 @@ jobs: typecheck: runs-on: ubuntu-latest + timeout-minutes: 10 steps: - uses: actions/checkout@v6 @@ -60,6 +63,7 @@ jobs: test: runs-on: ubuntu-latest + timeout-minutes: 10 steps: - uses: actions/checkout@v6 @@ -77,6 +81,7 @@ jobs: e2e: runs-on: ubuntu-latest + timeout-minutes: 15 steps: - uses: actions/checkout@v6 @@ -101,6 +106,7 @@ jobs: build: runs-on: ubuntu-latest + timeout-minutes: 10 steps: - uses: actions/checkout@v6 - name: Setup diff --git a/.github/workflows/conventions-semantic-pull-requests.yml b/.github/workflows/conventions-semantic-pull-requests.yml index 01eed5320..89aa7d9b2 100644 --- a/.github/workflows/conventions-semantic-pull-requests.yml +++ b/.github/workflows/conventions-semantic-pull-requests.yml @@ -13,7 +13,8 @@ permissions: jobs: validate-pull-request-title: runs-on: ubuntu-latest + timeout-minutes: 1 steps: - uses: amannn/action-semantic-pull-request@v6 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/crowdin-schedule-download.yml b/.github/workflows/crowdin-schedule-download.yml index a62f9e31d..fb0e1d7ba 100644 --- a/.github/workflows/crowdin-schedule-download.yml +++ b/.github/workflows/crowdin-schedule-download.yml @@ -10,6 +10,7 @@ permissions: jobs: download-crowdin-translations: + timeout-minutes: 5 runs-on: ubuntu-latest steps: diff --git a/.github/workflows/crowdin-upload.yml b/.github/workflows/crowdin-upload.yml index 4cdf60b66..4f8d48de6 100644 --- a/.github/workflows/crowdin-upload.yml +++ b/.github/workflows/crowdin-upload.yml @@ -14,6 +14,7 @@ jobs: # Don't run this action if the downloaded translations are being pushed if: "!contains(github.event.head_commit.message, 'chore(lang)')" runs-on: ubuntu-latest + timeout-minutes: 5 steps: - name: Checkout diff --git a/.github/workflows/deployment-docker-image.yml b/.github/workflows/deployment-docker-image.yml index d0479ae21..8335bb46c 100644 --- a/.github/workflows/deployment-docker-image.yml +++ b/.github/workflows/deployment-docker-image.yml @@ -32,12 +32,13 @@ jobs: release: name: Create tag and release runs-on: ubuntu-latest + timeout-minutes: 5 env: SKIP_RELEASE: ${{ github.event_name == 'workflow_dispatch' || github.ref_name == 'dev' }} outputs: version: ${{ steps.read-semver.outputs.version || steps.version-fallback.outputs.version }} git_ref: ${{ steps.read-git-ref.outputs.ref || github.ref }} - + skipped: ${{ env.SKIP_RELEASE }} steps: - run: echo "Skipping release for workflow_dispatch event" if: env.SKIP_RELEASE == 'true' @@ -64,7 +65,7 @@ jobs: - uses: actions/setup-node@v6 if: env.SKIP_RELEASE == 'false' with: - node-version: 24.11.1 + node-version: 24.12.0 cache: "pnpm" - run: npm i -g pnpm if: env.SKIP_RELEASE == 'false' @@ -112,6 +113,7 @@ jobs: name: Build docker image for amd64 needs: release runs-on: ubuntu-latest + timeout-minutes: 15 outputs: digest: ${{ steps.build.outputs.digest }} steps: @@ -146,11 +148,11 @@ jobs: outputs: type=image,"name=${{ env.GHCR_REPO }}",push-by-digest=true,name-canonical=true,push=true env: SKIP_ENV_VALIDATION: true - build-arm64: name: Build docker image for arm64 needs: release runs-on: ubuntu-24.04-arm + timeout-minutes: 20 outputs: digest: ${{ steps.build.outputs.digest }} steps: @@ -185,11 +187,42 @@ jobs: outputs: type=image,"name=${{ env.GHCR_REPO }}",push-by-digest=true,name-canonical=true,push=true env: SKIP_ENV_VALIDATION: true + extract-asset-amd64: + name: Extract amd64 asset from docker image + needs: [release, build-amd64] + runs-on: ubuntu-latest + timeout-minutes: 2 + steps: + - name: Extract amd64 + if: needs.release.outputs.skipped == 'false' + uses: homarr-labs/homarr/.github/actions/extract-build-artifact@dev + with: + digest: "${{ env.GHCR_REPO }}@${{ needs.build-amd64.outputs.digest }}" + architecture: amd64 + release-tag: ${{ needs.release.outputs.version }} + token: ${{ secrets.GITHUB_TOKEN }} + repository: ${{ github.repository }} + extract-asset-arm64: + name: Extract arm64 asset from docker image + needs: [release, build-arm64] + runs-on: ubuntu-24.04-arm + timeout-minutes: 2 + steps: + - name: Extract arm64 + if: needs.release.outputs.skipped == 'false' + uses: homarr-labs/homarr/.github/actions/extract-build-artifact@dev + with: + digest: "${{ env.GHCR_REPO }}@${{ needs.build-arm64.outputs.digest }}" + architecture: arm64 + release-tag: ${{ needs.release.outputs.version }} + token: ${{ secrets.GITHUB_TOKEN }} + repository: ${{ github.repository }} publish: name: Complete deployment and notify - needs: [release, build-amd64, build-arm64] + needs: [release, build-amd64, build-arm64, extract-asset-amd64, extract-asset-arm64] runs-on: ubuntu-latest + timeout-minutes: 5 env: NEXT_VERSION: ${{ needs.release.outputs.version }} DEPLOY_LATEST: ${{ github.ref_name == 'main' }} @@ -222,6 +255,13 @@ jobs: ${{ env.GHCR_REPO }}@${{ needs.build-amd64.outputs.digest }} \ ${{ env.GHCR_REPO }}@${{ needs.build-arm64.outputs.digest }} + - name: Publish release + if: needs.release.outputs.skipped == 'false' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release edit --repo ${{ github.repository }} ${{ needs.release.outputs.version }} --draft=false + - name: Discord notification env: DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} diff --git a/.github/workflows/deployment-weekly-release.yml b/.github/workflows/deployment-weekly-release.yml index 8eae236c4..869894a15 100644 --- a/.github/workflows/deployment-weekly-release.yml +++ b/.github/workflows/deployment-weekly-release.yml @@ -18,6 +18,7 @@ permissions: jobs: create-and-merge-pr: runs-on: ubuntu-latest + timeout-minutes: 2 steps: - name: Discord notification if: ${{ github.events.inputs.send-notifications }} diff --git a/.github/workflows/on-pr-prebuilt-debian-validate.yml b/.github/workflows/on-pr-prebuilt-debian-validate.yml new file mode 100644 index 000000000..e72acca1e --- /dev/null +++ b/.github/workflows/on-pr-prebuilt-debian-validate.yml @@ -0,0 +1,45 @@ +name: "[Deployments] Validate prebuilt debian dependencies" + +permissions: + contents: read + +on: + pull_request: + branches: ["*"] + paths: [".github/actions/prebuilt-debian/**", "deployments/prebuilt-debian/**"] + +jobs: + prebuilt-debian-validate-amd64: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v6 + - name: Validate prebuilt dependencies for amd64 + id: validate-amd64 + uses: ./.github/actions/prebuilt-debian + with: + architecture: amd64 + + - name: Check extracted files for amd64 + run: | + if [ ! -f "${{ steps.validate-amd64.outputs.path }}/better_sqlite3.node" ]; then + echo "better_sqlite3.node not found for amd64!" + exit 1 + fi + prebuilt-debian-validate-arm64: + runs-on: ubuntu-24.04- + timeout-minutes: 5 + steps: + - uses: actions/checkout@v6 + - name: Validate prebuilt dependencies for arm64 + id: validate-arm64 + uses: ./.github/actions/prebuilt-debian + with: + architecture: arm64 + + - name: Check extracted files for arm64 + run: | + if [ ! -f "${{ steps.validate-arm64.outputs.path }}/better_sqlite3.node" ]; then + echo "better_sqlite3.node not found for arm64!" + exit 1 + fi diff --git a/.github/workflows/on-pr-renovate-validate.yml b/.github/workflows/on-pr-renovate-validate.yml index ebfb57ed2..cd5b27c49 100644 --- a/.github/workflows/on-pr-renovate-validate.yml +++ b/.github/workflows/on-pr-renovate-validate.yml @@ -11,6 +11,7 @@ on: jobs: renovate-validate: runs-on: ubuntu-latest + timeout-minutes: 2 steps: - uses: actions/checkout@v6 - run: | diff --git a/.github/workflows/on-release.yml b/.github/workflows/on-release.yml index ac4277cde..c1f3739ef 100644 --- a/.github/workflows/on-release.yml +++ b/.github/workflows/on-release.yml @@ -8,6 +8,7 @@ jobs: trigger-docs-release: name: Trigger Documentation Release runs-on: ubuntu-latest + timeout-minutes: 2 steps: - name: Obtain token id: obtainToken @@ -39,6 +40,7 @@ jobs: update-bug-report-template: name: Update Bug Report Template runs-on: ubuntu-latest + timeout-minutes: 5 steps: - name: Obtain token id: obtainToken @@ -67,7 +69,7 @@ jobs: - name: Create Pull Request id: create-pull-request - uses: peter-evans/create-pull-request@v7 + uses: peter-evans/create-pull-request@v8 with: token: ${{ steps.obtainToken.outputs.token }} branch: update-bug-report-template diff --git a/.github/workflows/skip-renovate-stability-days.yml b/.github/workflows/skip-renovate-stability-days.yml index 5492103b7..5e4be4054 100644 --- a/.github/workflows/skip-renovate-stability-days.yml +++ b/.github/workflows/skip-renovate-stability-days.yml @@ -10,6 +10,7 @@ jobs: if: ${{ !startsWith(github.head_ref, 'renovate/') }} name: Skip Stability Days runs-on: ubuntu-latest + timeout-minutes: 1 steps: - name: Add status check env: diff --git a/.github/workflows/update-contributors.yml b/.github/workflows/update-contributors.yml index 91f087d9c..c3b7e53f0 100644 --- a/.github/workflows/update-contributors.yml +++ b/.github/workflows/update-contributors.yml @@ -14,6 +14,7 @@ permissions: jobs: update-contributors: runs-on: ubuntu-latest + timeout-minutes: 2 strategy: matrix: node-version: [22] @@ -53,7 +54,7 @@ jobs: - name: Create Pull Request id: create-pull-request - uses: peter-evans/create-pull-request@v7 + uses: peter-evans/create-pull-request@v8 with: token: ${{ steps.obtainToken.outputs.token }} branch: update-contributors diff --git a/.github/workflows/update-integration-list.yml b/.github/workflows/update-integration-list.yml index 5be197084..35e012c16 100644 --- a/.github/workflows/update-integration-list.yml +++ b/.github/workflows/update-integration-list.yml @@ -17,6 +17,7 @@ jobs: group: update-integration cancel-in-progress: false runs-on: ubuntu-latest + timeout-minutes: 5 steps: - name: Obtain token id: obtainToken @@ -44,7 +45,7 @@ jobs: - name: Create Pull Request id: create-pull-request - uses: peter-evans/create-pull-request@v7 + uses: peter-evans/create-pull-request@v8 with: token: ${{ steps.obtainToken.outputs.token }} branch: update-integrations-readme diff --git a/.nvmrc b/.nvmrc index 9e2934aa3..248216ad5 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -24.11.1 +24.12.0 diff --git a/.releaserc.json b/.releaserc.json index e2414a583..0f2b07b43 100644 --- a/.releaserc.json +++ b/.releaserc.json @@ -44,6 +44,7 @@ { "successComment": false, "failComment": false, + "draftRelease": true, "releaseBodyTemplate": "<%= _.truncate(nextRelease.notes, { 'length': 124000, 'omission': '' }) %>" } ] diff --git a/CHANGELOG.md b/CHANGELOG.md index d41feec90..2c8a7b694 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,30 @@ +## [1.46.0](https://github.com/homarr-labs/homarr/compare/v1.45.3...v1.46.0) (2025-12-12) + +### Features + +* **docker:** add widget options to sort containers ([#4636](https://github.com/homarr-labs/homarr/issues/4636)) ([dd24925](https://github.com/homarr-labs/homarr/commit/dd24925d906dc13d73c52a47fdd07bec89798789)) + +### Bug Fixes + +* add autocomplete and id attributes for password manager compatibility ([#4602](https://github.com/homarr-labs/homarr/issues/4602)) ([681aee0](https://github.com/homarr-labs/homarr/commit/681aee05f07a6e09371ed089d2b9ef361494c416)) +* **deps:** update dependency @tanstack/react-query to ^5.90.12 ([#4615](https://github.com/homarr-labs/homarr/issues/4615)) ([efc0384](https://github.com/homarr-labs/homarr/commit/efc0384b372fadafb65f61f715f08edd2a8f8573)) +* **deps:** update dependency better-sqlite3 to ^12.5.0 ([#4569](https://github.com/homarr-labs/homarr/issues/4569)) ([a20248d](https://github.com/homarr-labs/homarr/commit/a20248d0b689ab1ca475f606edfca458897ce442)) +* **deps:** update dependency drizzle-kit to ^0.31.8 ([#4618](https://github.com/homarr-labs/homarr/issues/4618)) ([5cbab28](https://github.com/homarr-labs/homarr/commit/5cbab28099d41b8ade6cc8a42e69c4191310296c)) +* **deps:** update dependency drizzle-orm to ^0.45.0 ([#4619](https://github.com/homarr-labs/homarr/issues/4619)) ([cab5d19](https://github.com/homarr-labs/homarr/commit/cab5d19141a38fbb977f4ef6e9c2ddbfdfea6aff)) +* **deps:** update dependency ldapts to v8.0.12 ([#4613](https://github.com/homarr-labs/homarr/issues/4613)) ([4689ec9](https://github.com/homarr-labs/homarr/commit/4689ec9933e5286c0d19c66989023b5b3376ffb1)) +* **deps:** update dependency ldapts to v8.0.13 ([#4626](https://github.com/homarr-labs/homarr/issues/4626)) ([8a14a6c](https://github.com/homarr-labs/homarr/commit/8a14a6cb456a6f16975372f64451e3b54ebfd88d)) +* **deps:** update dependency ldapts to v8.0.14 ([#4627](https://github.com/homarr-labs/homarr/issues/4627)) ([e37325f](https://github.com/homarr-labs/homarr/commit/e37325f5e0314b13d031fba1bff591b881baf8d1)) +* **deps:** update dependency winston to v3.19.0 ([#4632](https://github.com/homarr-labs/homarr/issues/4632)) ([3b1c564](https://github.com/homarr-labs/homarr/commit/3b1c564a5abbf1d1fca6ecab46e173c8f6938130)) +* **deps:** update next monorepo to v16.0.10 [security] ([#4634](https://github.com/homarr-labs/homarr/issues/4634)) ([2b2c0e7](https://github.com/homarr-labs/homarr/commit/2b2c0e7e3a813525bd977276df6e5594bc283055)) +* **deps:** update react monorepo to v19.2.1 ([#4608](https://github.com/homarr-labs/homarr/issues/4608)) ([f6d16fe](https://github.com/homarr-labs/homarr/commit/f6d16fe44bf239fb85a4ee3e9ae6491131315dce)) +* **deps:** update tiptap monorepo to v3 (major) ([#3612](https://github.com/homarr-labs/homarr/issues/3612)) ([d111eee](https://github.com/homarr-labs/homarr/commit/d111eee0e0a41e55de87049f3cb0a9154ae82ea1)) +* **deps:** update turbo monorepo to ^2.6.2 ([#4612](https://github.com/homarr-labs/homarr/issues/4612)) ([7de45e6](https://github.com/homarr-labs/homarr/commit/7de45e6fe50ccf4333959f924889463a22316984)) +* **deps:** update turbo monorepo to ^2.6.3 ([#4622](https://github.com/homarr-labs/homarr/issues/4622)) ([85689ca](https://github.com/homarr-labs/homarr/commit/85689caf6c25b80cb4ce04a0baf2980a1a4deece)) +* format issues ([1dd9694](https://github.com/homarr-labs/homarr/commit/1dd9694719ec4dcefe3c8d221015ba9aecede78c)) +* improve run script ([#4609](https://github.com/homarr-labs/homarr/issues/4609)) ([d084dfb](https://github.com/homarr-labs/homarr/commit/d084dfb4705fca68bb9f9465ec9564050d0b04b2)) +* **plex:** test connection not fully checking connection ([#4617](https://github.com/homarr-labs/homarr/issues/4617)) ([6a90839](https://github.com/homarr-labs/homarr/commit/6a908397cd8a6189123b91ea9d68f683af2bed05)) +* **transmission:** total download incorrect for cross seeded torrents ([#3211](https://github.com/homarr-labs/homarr/issues/3211)) ([0b4ca93](https://github.com/homarr-labs/homarr/commit/0b4ca93aa13e9838b481daed5703640d30a52ffc)) + ## [1.45.3](https://github.com/homarr-labs/homarr/compare/v1.45.2...v1.45.3) (2025-12-08) ### Bug Fixes diff --git a/CODEOWNERS b/CODEOWNERS index 149ddf1ff..59516a4fa 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1,9 @@ +# ── CODEOWNERS ─────────────────────────────────────── * @homarr-labs/maintainers + +# Exempt Renovate‑managed files (no owners) +package.json @homarr-labs/none +package-lock.json @homarr-labs/none +pnpm-lock.yaml @homarr-labs/none +Dockerfile @homarr-labs/none +docker-compose.yml @homarr-labs/none diff --git a/Dockerfile b/Dockerfile index 50075fcb5..4cf6a8533 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:24.11.1-alpine AS base +FROM node:24.12.0-alpine AS base FROM base AS builder RUN apk add --no-cache libc6-compat diff --git a/apps/nextjs/next.config.ts b/apps/nextjs/next.config.ts index 7b9bcb915..a3dabe151 100644 --- a/apps/nextjs/next.config.ts +++ b/apps/nextjs/next.config.ts @@ -1,8 +1,8 @@ // Importing env files here to validate on build import "@homarr/auth/env"; -import "@homarr/db/env"; +import "@homarr/core/infrastructure/db/env"; import "@homarr/common/env"; -import "@homarr/log/env"; +import "@homarr/core/infrastructure/logs/env"; import "@homarr/docker/env"; import type { NextConfig } from "next"; diff --git a/apps/nextjs/package.json b/apps/nextjs/package.json index 602f449bd..1a9f0641c 100644 --- a/apps/nextjs/package.json +++ b/apps/nextjs/package.json @@ -23,7 +23,6 @@ "@homarr/api": "workspace:^0.1.0", "@homarr/auth": "workspace:^0.1.0", "@homarr/boards": "workspace:^0.1.0", - "@homarr/certificates": "workspace:^0.1.0", "@homarr/common": "workspace:^0.1.0", "@homarr/core": "workspace:^0.1.0", "@homarr/cron-job-status": "workspace:^0.1.0", @@ -36,7 +35,6 @@ "@homarr/icons": "workspace:^0.1.0", "@homarr/image-proxy": "workspace:^0.1.0", "@homarr/integrations": "workspace:^0.1.0", - "@homarr/log": "workspace:^", "@homarr/modals": "workspace:^0.1.0", "@homarr/modals-collection": "workspace:^0.1.0", "@homarr/notifications": "workspace:^0.1.0", @@ -50,22 +48,22 @@ "@homarr/ui": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0", "@homarr/widgets": "workspace:^0.1.0", - "@mantine/colors-generator": "^8.3.9", - "@mantine/core": "^8.3.9", - "@mantine/dropzone": "^8.3.9", - "@mantine/hooks": "^8.3.9", - "@mantine/modals": "^8.3.9", - "@mantine/tiptap": "^8.3.9", + "@mantine/colors-generator": "^8.3.10", + "@mantine/core": "^8.3.10", + "@mantine/dropzone": "^8.3.10", + "@mantine/hooks": "^8.3.10", + "@mantine/modals": "^8.3.10", + "@mantine/tiptap": "^8.3.10", "@million/lint": "1.0.14", "@scalar/api-reference-react": "^0.8.11", "@tabler/icons-react": "^3.35.0", "@tanstack/react-query": "^5.90.12", "@tanstack/react-query-devtools": "^5.91.1", "@tanstack/react-query-next-experimental": "^5.91.0", - "@trpc/client": "^11.7.2", - "@trpc/next": "^11.7.2", - "@trpc/react-query": "^11.7.2", - "@trpc/server": "^11.7.2", + "@trpc/client": "^11.8.0", + "@trpc/next": "^11.8.0", + "@trpc/react-query": "^11.8.0", + "@trpc/server": "^11.8.0", "@xterm/addon-canvas": "^0.7.0", "@xterm/addon-fit": "0.10.0", "@xterm/xterm": "^5.5.0", @@ -76,17 +74,17 @@ "dotenv": "^17.2.3", "flag-icons": "^7.5.0", "glob": "^13.0.0", - "isomorphic-dompurify": "^2.33.0", - "jotai": "^2.15.2", + "isomorphic-dompurify": "^2.34.0", + "jotai": "^2.16.0", "mantine-react-table": "2.0.0-beta.9", - "next": "16.0.7", + "next": "16.0.10", "postcss-preset-mantine": "^1.18.0", "prismjs": "^1.30.0", - "react": "19.2.1", - "react-dom": "19.2.1", + "react": "19.2.3", + "react-dom": "19.2.3", "react-error-boundary": "^6.0.0", "react-simple-code-editor": "^0.14.1", - "sass": "^1.94.2", + "sass": "^1.96.0", "superjson": "2.2.6", "use-deep-compare-effect": "^1.8.1", "zod": "^4.1.13" @@ -96,12 +94,12 @@ "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", "@types/chroma-js": "3.1.2", - "@types/node": "^24.10.1", + "@types/node": "^24.10.4", "@types/prismjs": "^1.26.5", "@types/react": "19.2.7", "@types/react-dom": "19.2.3", "concurrently": "^9.2.1", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "node-loader": "^2.1.0", "prettier": "^3.7.4", "typescript": "^5.9.3" diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/_creator.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/_creator.tsx index 80d52a50f..64c9289e3 100644 --- a/apps/nextjs/src/app/[locale]/boards/(content)/_creator.tsx +++ b/apps/nextjs/src/app/[locale]/boards/(content)/_creator.tsx @@ -11,8 +11,9 @@ import { IntegrationProvider } from "@homarr/auth/client"; import { auth } from "@homarr/auth/next"; import { getIntegrationsWithPermissionsAsync } from "@homarr/auth/server"; import { isNullOrWhitespace } from "@homarr/common"; +import { createLogger } from "@homarr/core/infrastructure/logs"; +import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; import type { WidgetKind } from "@homarr/definitions"; -import { logger } from "@homarr/log"; import { getI18n } from "@homarr/translation/server"; import { prefetchForKindAsync } from "@homarr/widgets/prefetch"; @@ -22,6 +23,8 @@ import type { Board, Item } from "../_types"; import { DynamicClientBoard } from "./_dynamic-client"; import { BoardContentHeaderActions } from "./_header-actions"; +const logger = createLogger({ module: "createBoardContentPage" }); + export type Params = Record; interface Props { @@ -57,7 +60,13 @@ export const createBoardContentPage = >( for (const [kind, items] of itemsMap) { await prefetchForKindAsync(kind, queryClient, items).catch((error) => { - logger.error(new Error("Failed to prefetch widget", { cause: error })); + logger.error( + new ErrorWithMetadata( + "Failed to prefetch widget", + { widgetKind: kind, itemCount: items.length }, + { cause: error }, + ), + ); }); } diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx index 9ee773d16..ce6866ef3 100644 --- a/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx +++ b/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx @@ -44,7 +44,7 @@ export const BoardContentHeaderActions = () => { const { hasChangeAccess } = useBoardPermissions(board); if (!hasChangeAccess) { - return null; // Hide actions for user without access + return ; } return ( diff --git a/apps/nextjs/src/app/[locale]/boards/_layout-creator.tsx b/apps/nextjs/src/app/[locale]/boards/_layout-creator.tsx index da1880963..f377c932e 100644 --- a/apps/nextjs/src/app/[locale]/boards/_layout-creator.tsx +++ b/apps/nextjs/src/app/[locale]/boards/_layout-creator.tsx @@ -6,7 +6,7 @@ import { TRPCError } from "@trpc/server"; import { auth } from "@homarr/auth/next"; import { BoardProvider } from "@homarr/boards/context"; import { EditModeProvider } from "@homarr/boards/edit-mode"; -import { logger } from "@homarr/log"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { MainHeader } from "~/components/layout/header"; import { BoardLogoWithTitle } from "~/components/layout/logo/board-logo"; @@ -18,6 +18,8 @@ import { CustomCss } from "./(content)/_custom-css"; import { BoardReadyProvider } from "./(content)/_ready-context"; import { BoardMantineProvider } from "./(content)/_theme"; +const logger = createLogger({ module: "createBoardLayout" }); + interface CreateBoardLayoutProps { headerActions: JSX.Element; getInitialBoardAsync: (params: TParams) => Promise; diff --git a/apps/nextjs/src/app/[locale]/manage/tools/certificates/hostnames/page.tsx b/apps/nextjs/src/app/[locale]/manage/tools/certificates/hostnames/page.tsx index 2b9aa6ac6..8eda3dcb5 100644 --- a/apps/nextjs/src/app/[locale]/manage/tools/certificates/hostnames/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/tools/certificates/hostnames/page.tsx @@ -16,7 +16,7 @@ import { import { IconCertificateOff } from "@tabler/icons-react"; import { auth } from "@homarr/auth/next"; -import { getTrustedCertificateHostnamesAsync } from "@homarr/certificates/server"; +import { getTrustedCertificateHostnamesAsync } from "@homarr/core/infrastructure/certificates"; import { getI18n } from "@homarr/translation/server"; import { Link } from "@homarr/ui"; diff --git a/apps/nextjs/src/app/[locale]/manage/tools/certificates/page.tsx b/apps/nextjs/src/app/[locale]/manage/tools/certificates/page.tsx index fc9a29dd6..cf1730736 100644 --- a/apps/nextjs/src/app/[locale]/manage/tools/certificates/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/tools/certificates/page.tsx @@ -5,8 +5,8 @@ import { IconAlertTriangle, IconCertificate, IconCertificateOff } from "@tabler/ import dayjs from "dayjs"; import { auth } from "@homarr/auth/next"; -import { loadCustomRootCertificatesAsync } from "@homarr/certificates/server"; import { getMantineColor } from "@homarr/common"; +import { loadCustomRootCertificatesAsync } from "@homarr/core/infrastructure/certificates"; import type { SupportedLanguage } from "@homarr/translation"; import { getI18n } from "@homarr/translation/server"; import { Link } from "@homarr/ui"; diff --git a/apps/nextjs/src/app/[locale]/manage/tools/logs/level-selection.tsx b/apps/nextjs/src/app/[locale]/manage/tools/logs/level-selection.tsx index fdfe23e18..71846ca02 100644 --- a/apps/nextjs/src/app/[locale]/manage/tools/logs/level-selection.tsx +++ b/apps/nextjs/src/app/[locale]/manage/tools/logs/level-selection.tsx @@ -2,8 +2,8 @@ import { Select } from "@mantine/core"; -import type { LogLevel } from "@homarr/log/constants"; -import { logLevelConfiguration, logLevels } from "@homarr/log/constants"; +import type { LogLevel } from "@homarr/core/infrastructure/logs/constants"; +import { logLevelConfiguration, logLevels } from "@homarr/core/infrastructure/logs/constants"; import { useI18n } from "@homarr/translation/client"; import { useLogContext } from "./log-context"; diff --git a/apps/nextjs/src/app/[locale]/manage/tools/logs/log-context.tsx b/apps/nextjs/src/app/[locale]/manage/tools/logs/log-context.tsx index 97889af9a..9a58ffaaa 100644 --- a/apps/nextjs/src/app/[locale]/manage/tools/logs/log-context.tsx +++ b/apps/nextjs/src/app/[locale]/manage/tools/logs/log-context.tsx @@ -3,8 +3,8 @@ import type { PropsWithChildren } from "react"; import { createContext, useContext, useMemo, useState } from "react"; -import type { LogLevel } from "@homarr/log/constants"; -import { logLevels } from "@homarr/log/constants"; +import type { LogLevel } from "@homarr/core/infrastructure/logs/constants"; +import { logLevels } from "@homarr/core/infrastructure/logs/constants"; const LogContext = createContext<{ level: LogLevel; diff --git a/apps/nextjs/src/app/[locale]/manage/tools/logs/page.tsx b/apps/nextjs/src/app/[locale]/manage/tools/logs/page.tsx index e13959412..d672947e4 100644 --- a/apps/nextjs/src/app/[locale]/manage/tools/logs/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/tools/logs/page.tsx @@ -7,7 +7,7 @@ import "@xterm/xterm/css/xterm.css"; import { notFound } from "next/navigation"; import { auth } from "@homarr/auth/next"; -import { env } from "@homarr/log/env"; +import { logsEnv } from "@homarr/core/infrastructure/logs/env"; import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb"; import { fullHeightWithoutHeaderAndFooter } from "~/constants"; @@ -35,7 +35,7 @@ export default async function LogsManagementPage() { } return ( - + diff --git a/apps/nextjs/src/app/api/[...trpc]/route.ts b/apps/nextjs/src/app/api/[...trpc]/route.ts index ea992afda..fa5ced6fc 100644 --- a/apps/nextjs/src/app/api/[...trpc]/route.ts +++ b/apps/nextjs/src/app/api/[...trpc]/route.ts @@ -6,9 +6,12 @@ import { appRouter, createTRPCContext } from "@homarr/api"; import type { Session } from "@homarr/auth"; import { hashPasswordAsync } from "@homarr/auth"; import { createSessionAsync } from "@homarr/auth/server"; +import { createLogger } from "@homarr/core/infrastructure/logs"; +import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; import { db, eq } from "@homarr/db"; import { apiKeys } from "@homarr/db/schema"; -import { logger } from "@homarr/log"; + +const logger = createLogger({ module: "trpcOpenApiRoute" }); const handlerAsync = async (req: NextRequest) => { const apiKeyHeaderValue = req.headers.get("ApiKey"); @@ -27,7 +30,7 @@ const handlerAsync = async (req: NextRequest) => { router: appRouter, createContext: () => createTRPCContext({ session, headers: req.headers }), onError({ error, path, type }) { - logger.error(new Error(`tRPC Error with ${type} on '${path}'`, { cause: error.cause })); + logger.error(new ErrorWithMetadata("tRPC Error occured", { path, type }, { cause: error })); }, }); }; @@ -48,9 +51,10 @@ const getSessionOrDefaultFromHeadersAsync = async ( const [apiKeyId, apiKey] = apiKeyHeaderValue.split("."); if (!apiKeyId || !apiKey) { - logger.warn( - `An attempt to authenticate over API has failed due to invalid API key format ip='${ipAdress}' userAgent='${userAgent}'`, - ); + logger.warn("An attempt to authenticate over API has failed due to invalid API key format", { + ipAdress, + userAgent, + }); return null; } @@ -74,18 +78,21 @@ const getSessionOrDefaultFromHeadersAsync = async ( }); if (!apiKeyFromDb) { - logger.warn(`An attempt to authenticate over API has failed ip='${ipAdress}' userAgent='${userAgent}'`); + logger.warn("An attempt to authenticate over API has failed", { ipAdress, userAgent }); return null; } const hashedApiKey = await hashPasswordAsync(apiKey, apiKeyFromDb.salt); if (apiKeyFromDb.apiKey !== hashedApiKey) { - logger.warn(`An attempt to authenticate over API has failed ip='${ipAdress}' userAgent='${userAgent}'`); + logger.warn("An attempt to authenticate over API has failed", { ipAdress, userAgent }); return null; } - logger.info(`Read session from API request and found user ${apiKeyFromDb.user.name} (${apiKeyFromDb.user.id})`); + logger.info("Read session from API request and found user", { + name: apiKeyFromDb.user.name, + id: apiKeyFromDb.user.id, + }); return await createSessionAsync(db, apiKeyFromDb.user); }; diff --git a/apps/nextjs/src/app/api/auth/[...nextauth]/route.ts b/apps/nextjs/src/app/api/auth/[...nextauth]/route.ts index e04a79859..37a0b7feb 100644 --- a/apps/nextjs/src/app/api/auth/[...nextauth]/route.ts +++ b/apps/nextjs/src/app/api/auth/[...nextauth]/route.ts @@ -1,8 +1,10 @@ import { NextRequest } from "next/server"; import { createHandlersAsync } from "@homarr/auth"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import type { SupportedAuthProvider } from "@homarr/definitions"; -import { logger } from "@homarr/log"; + +const logger = createLogger({ module: "nextAuthRoute" }); export const GET = async (req: NextRequest) => { const { handlers } = await createHandlersAsync(extractProvider(req), isSecureCookieEnabled(req)); diff --git a/apps/nextjs/src/app/api/health/live/route.ts b/apps/nextjs/src/app/api/health/live/route.ts index 57cc9b207..2576b3f8e 100644 --- a/apps/nextjs/src/app/api/health/live/route.ts +++ b/apps/nextjs/src/app/api/health/live/route.ts @@ -1,13 +1,16 @@ import { performance } from "perf_hooks"; +import { createLogger } from "@homarr/core/infrastructure/logs"; +import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; import { db } from "@homarr/db"; -import { logger } from "@homarr/log"; import { handshakeAsync } from "@homarr/redis"; +const logger = createLogger({ module: "healthLiveRoute" }); + export async function GET() { const timeBeforeHealthCheck = performance.now(); const response = await executeAndAggregateAllHealthChecksAsync(); - logger.info(`Completed healthcheck after ${performance.now() - timeBeforeHealthCheck}ms`); + logger.info("Completed healthcheck", { elapsed: `${performance.now() - timeBeforeHealthCheck}ms` }); if (response.status === "healthy") { return new Response(JSON.stringify(response), { @@ -73,7 +76,7 @@ const executeHealthCheckSafelyAsync = async ( }; } catch (error) { // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - logger.error(`Healthcheck '${name}' has failed: ${error}`); + logger.error(new ErrorWithMetadata("Healthcheck failed", { name }, { cause: error })); return { status: "unhealthy", values: { diff --git a/apps/nextjs/src/app/api/trpc/[trpc]/route.ts b/apps/nextjs/src/app/api/trpc/[trpc]/route.ts index f35afb7a3..65ebb353d 100644 --- a/apps/nextjs/src/app/api/trpc/[trpc]/route.ts +++ b/apps/nextjs/src/app/api/trpc/[trpc]/route.ts @@ -3,7 +3,10 @@ import { fetchRequestHandler } from "@trpc/server/adapters/fetch"; import { appRouter, createTRPCContext } from "@homarr/api"; import { trpcPath } from "@homarr/api/shared"; import { auth } from "@homarr/auth/next"; -import { logger } from "@homarr/log"; +import { createLogger } from "@homarr/core/infrastructure/logs"; +import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; + +const logger = createLogger({ module: "trpcRoute" }); /** * Configure basic CORS headers @@ -31,7 +34,7 @@ const handler = auth(async (req) => { req, createContext: () => createTRPCContext({ session: req.auth, headers: req.headers }), onError({ error, path, type }) { - logger.error(new Error(`tRPC Error with ${type} on '${path}'`, { cause: error.cause })); + logger.error(new ErrorWithMetadata("tRPC Error occured", { path, type }, { cause: error })); }, }); diff --git a/apps/nextjs/src/errors/trpc-catch-error.ts b/apps/nextjs/src/errors/trpc-catch-error.ts index 1fb766745..3e38e9cfb 100644 --- a/apps/nextjs/src/errors/trpc-catch-error.ts +++ b/apps/nextjs/src/errors/trpc-catch-error.ts @@ -3,7 +3,9 @@ import "server-only"; import { notFound, redirect } from "next/navigation"; import { TRPCError } from "@trpc/server"; -import { logger } from "@homarr/log"; +import { createLogger } from "@homarr/core/infrastructure/logs"; + +const logger = createLogger({ module: "trpcCatchError" }); export const catchTrpcNotFound = (err: unknown) => { if (err instanceof TRPCError && err.code === "NOT_FOUND") { diff --git a/apps/tasks/package.json b/apps/tasks/package.json index 1fe2e3f19..3d663aa84 100644 --- a/apps/tasks/package.json +++ b/apps/tasks/package.json @@ -23,6 +23,7 @@ "@homarr/analytics": "workspace:^0.1.0", "@homarr/auth": "workspace:^0.1.0", "@homarr/common": "workspace:^0.1.0", + "@homarr/core": "workspace:^", "@homarr/cron-job-api": "workspace:^0.1.0", "@homarr/cron-jobs": "workspace:^0.1.0", "@homarr/cron-jobs-core": "workspace:^0.1.0", @@ -30,7 +31,6 @@ "@homarr/definitions": "workspace:^0.1.0", "@homarr/icons": "workspace:^0.1.0", "@homarr/integrations": "workspace:^0.1.0", - "@homarr/log": "workspace:^", "@homarr/ping": "workspace:^0.1.0", "@homarr/redis": "workspace:^0.1.0", "@homarr/request-handler": "workspace:^0.1.0", @@ -47,10 +47,10 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "@types/node": "^24.10.1", + "@types/node": "^24.10.4", "dotenv-cli": "^11.0.0", "esbuild": "^0.27.1", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "prettier": "^3.7.4", "tsx": "4.20.4", "typescript": "^5.9.3" diff --git a/apps/tasks/src/job-manager.ts b/apps/tasks/src/job-manager.ts index e19f2c315..9e23c7d36 100644 --- a/apps/tasks/src/job-manager.ts +++ b/apps/tasks/src/job-manager.ts @@ -1,11 +1,13 @@ import { schedule, validate as validateCron } from "node-cron"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import type { IJobManager } from "@homarr/cron-job-api"; import type { jobGroup as cronJobGroup, JobGroupKeys } from "@homarr/cron-jobs"; import type { Database, InferInsertModel } from "@homarr/db"; import { eq } from "@homarr/db"; import { cronJobConfigurations } from "@homarr/db/schema"; -import { logger } from "@homarr/log"; + +const logger = createLogger({ module: "jobManager" }); export class JobManager implements IJobManager { constructor( @@ -23,7 +25,7 @@ export class JobManager implements IJobManager { await this.jobGroup.stopAsync(name); } public async updateIntervalAsync(name: JobGroupKeys, cron: string): Promise { - logger.info(`Updating cron job interval name="${name}" expression="${cron}"`); + logger.info("Updating cron job interval", { name, expression: cron }); const job = this.jobGroup.getJobRegistry().get(name); if (!job) throw new Error(`Job ${name} not found`); if (!validateCron(cron)) { @@ -38,22 +40,22 @@ export class JobManager implements IJobManager { name, }), ); - logger.info(`Cron job interval updated name="${name}" expression="${cron}"`); + logger.info("Cron job interval updated", { name, expression: cron }); } public async disableAsync(name: JobGroupKeys): Promise { - logger.info(`Disabling cron job name="${name}"`); + logger.info("Disabling cron job", { name }); const job = this.jobGroup.getJobRegistry().get(name); if (!job) throw new Error(`Job ${name} not found`); await this.updateConfigurationAsync(name, { isEnabled: false }); await this.jobGroup.stopAsync(name); - logger.info(`Cron job disabled name="${name}"`); + logger.info("Cron job disabled", { name }); } public async enableAsync(name: JobGroupKeys): Promise { - logger.info(`Enabling cron job name="${name}"`); + logger.info("Enabling cron job", { name }); await this.updateConfigurationAsync(name, { isEnabled: true }); await this.jobGroup.startAsync(name); - logger.info(`Cron job enabled name="${name}"`); + logger.info("Cron job enabled", { name }); } private async updateConfigurationAsync( @@ -64,9 +66,11 @@ export class JobManager implements IJobManager { where: (table, { eq }) => eq(table.name, name), }); - logger.debug( - `Updating cron job configuration name="${name}" configuration="${JSON.stringify(configuration)}" exists="${Boolean(existingConfig)}"`, - ); + logger.debug("Updating cron job configuration", { + name, + configuration: JSON.stringify(configuration), + exists: Boolean(existingConfig), + }); if (existingConfig) { await this.db @@ -74,7 +78,10 @@ export class JobManager implements IJobManager { // prevent updating the name, as it is the primary key .set({ ...configuration, name: undefined }) .where(eq(cronJobConfigurations.name, name)); - logger.debug(`Cron job configuration updated name="${name}" configuration="${JSON.stringify(configuration)}"`); + logger.debug("Cron job configuration updated", { + name, + configuration: JSON.stringify(configuration), + }); return; } @@ -86,7 +93,10 @@ export class JobManager implements IJobManager { cronExpression: configuration.cronExpression ?? job.cronExpression, isEnabled: configuration.isEnabled ?? true, }); - logger.debug(`Cron job configuration updated name="${name}" configuration="${JSON.stringify(configuration)}"`); + logger.debug("Cron job configuration updated", { + name, + configuration: JSON.stringify(configuration), + }); } public async getAllAsync(): Promise< diff --git a/apps/tasks/src/main.ts b/apps/tasks/src/main.ts index ad6181880..4c60f1646 100644 --- a/apps/tasks/src/main.ts +++ b/apps/tasks/src/main.ts @@ -5,16 +5,19 @@ import type { FastifyTRPCPluginOptions } from "@trpc/server/adapters/fastify"; import { fastifyTRPCPlugin } from "@trpc/server/adapters/fastify"; import fastify from "fastify"; +import { createLogger } from "@homarr/core/infrastructure/logs"; +import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; import type { JobRouter } from "@homarr/cron-job-api"; import { jobRouter } from "@homarr/cron-job-api"; import { CRON_JOB_API_KEY_HEADER, CRON_JOB_API_PATH, CRON_JOB_API_PORT } from "@homarr/cron-job-api/constants"; import { jobGroup } from "@homarr/cron-jobs"; import { db } from "@homarr/db"; -import { logger } from "@homarr/log"; import { JobManager } from "./job-manager"; import { onStartAsync } from "./on-start"; +const logger = createLogger({ module: "tasksMain" }); + const server = fastify({ maxParamLength: 5000, }); @@ -27,7 +30,7 @@ server.register(fastifyTRPCPlugin, { apiKey: req.headers[CRON_JOB_API_KEY_HEADER] as string | undefined, }), onError({ path, error }) { - logger.error(new Error(`Error in tasks tRPC handler path="${path}"`, { cause: error })); + logger.error(new ErrorWithMetadata("Error in tasks tRPC handler", { path }, { cause: error })); }, } satisfies FastifyTRPCPluginOptions["trpcOptions"], }); @@ -39,9 +42,11 @@ void (async () => { try { await server.listen({ port: CRON_JOB_API_PORT }); - logger.info(`Tasks web server started successfully port="${CRON_JOB_API_PORT}"`); + logger.info("Tasks web server started successfully", { port: CRON_JOB_API_PORT }); } catch (err) { - logger.error(new Error(`Failed to start tasks web server port="${CRON_JOB_API_PORT}"`, { cause: err })); + logger.error( + new ErrorWithMetadata("Failed to start tasks web server", { port: CRON_JOB_API_PORT }, { cause: err }), + ); process.exit(1); } })(); diff --git a/apps/tasks/src/on-start/invalidate-update-checker-cache.ts b/apps/tasks/src/on-start/invalidate-update-checker-cache.ts index 7bbda3dec..470989dcf 100644 --- a/apps/tasks/src/on-start/invalidate-update-checker-cache.ts +++ b/apps/tasks/src/on-start/invalidate-update-checker-cache.ts @@ -1,7 +1,7 @@ -import { logger } from "@homarr/log"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { updateCheckerRequestHandler } from "@homarr/request-handler/update-checker"; -const localLogger = logger.child({ module: "invalidateUpdateCheckerCache" }); +const logger = createLogger({ module: "invalidateUpdateCheckerCache" }); /** * Invalidates the update checker cache on startup to ensure fresh data. @@ -11,8 +11,8 @@ export async function invalidateUpdateCheckerCacheAsync() { try { const handler = updateCheckerRequestHandler.handler({}); await handler.invalidateAsync(); - localLogger.debug("Update checker cache invalidated"); + logger.debug("Update checker cache invalidated"); } catch (error) { - localLogger.error(new Error("Failed to invalidate update checker cache", { cause: error })); + logger.error(new Error("Failed to invalidate update checker cache", { cause: error })); } } diff --git a/apps/tasks/src/on-start/session-cleanup.ts b/apps/tasks/src/on-start/session-cleanup.ts index 4dfe46341..3ef6d9cdc 100644 --- a/apps/tasks/src/on-start/session-cleanup.ts +++ b/apps/tasks/src/on-start/session-cleanup.ts @@ -1,10 +1,10 @@ import { env } from "@homarr/auth/env"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { db, eq, inArray } from "@homarr/db"; import { sessions, users } from "@homarr/db/schema"; import { supportedAuthProviders } from "@homarr/definitions"; -import { logger } from "@homarr/log"; -const localLogger = logger.child({ module: "sessionCleanup" }); +const logger = createLogger({ module: "sessionCleanup" }); /** * Deletes sessions for users that have inactive auth providers. @@ -29,11 +29,13 @@ export async function cleanupSessionsAsync() { await db.delete(sessions).where(inArray(sessions.userId, userIds)); if (sessionsWithInactiveProviders.length > 0) { - localLogger.info(`Deleted sessions for inactive providers count=${userIds.length}`); + logger.info("Deleted sessions for inactive providers", { + count: userIds.length, + }); } else { - localLogger.debug("No sessions to delete"); + logger.debug("No sessions to delete"); } } catch (error) { - localLogger.error(new Error("Failed to clean up sessions", { cause: error })); + logger.error(new Error("Failed to clean up sessions", { cause: error })); } } diff --git a/apps/tasks/src/overrides.ts b/apps/tasks/src/overrides.ts index f05432c02..684531f81 100644 --- a/apps/tasks/src/overrides.ts +++ b/apps/tasks/src/overrides.ts @@ -1,6 +1,5 @@ import { setGlobalDispatcher } from "undici"; -import { LoggingAgent } from "@homarr/common/server"; +import { UndiciHttpAgent } from "@homarr/core/infrastructure/http"; -const agent = new LoggingAgent(); -setGlobalDispatcher(agent); +setGlobalDispatcher(new UndiciHttpAgent()); diff --git a/apps/websocket/package.json b/apps/websocket/package.json index 3f77645a8..522ffb3de 100644 --- a/apps/websocket/package.json +++ b/apps/websocket/package.json @@ -20,9 +20,9 @@ "@homarr/api": "workspace:^0.1.0", "@homarr/auth": "workspace:^0.1.0", "@homarr/common": "workspace:^0.1.0", + "@homarr/core": "workspace:^", "@homarr/db": "workspace:^0.1.0", "@homarr/definitions": "workspace:^0.1.0", - "@homarr/log": "workspace:^", "@homarr/redis": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0", "dotenv": "^17.2.3", @@ -35,7 +35,7 @@ "@homarr/tsconfig": "workspace:^0.1.0", "@types/ws": "^8.18.1", "esbuild": "^0.27.1", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "prettier": "^3.7.4", "typescript": "^5.9.3" } diff --git a/apps/websocket/src/main.ts b/apps/websocket/src/main.ts index f819c3dff..45dfe50a2 100644 --- a/apps/websocket/src/main.ts +++ b/apps/websocket/src/main.ts @@ -4,8 +4,10 @@ import { WebSocketServer } from "ws"; import { appRouter, createTRPCContext } from "@homarr/api/websocket"; import { getSessionFromToken, sessionTokenCookieName } from "@homarr/auth"; import { parseCookies } from "@homarr/common"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { db } from "@homarr/db"; -import { logger } from "@homarr/log"; + +const logger = createLogger({ module: "websocketMain" }); const wss = new WebSocketServer({ port: 3001, diff --git a/deployments/prebuilt-debian/Dockerfile b/deployments/prebuilt-debian/Dockerfile new file mode 100644 index 000000000..414af691c --- /dev/null +++ b/deployments/prebuilt-debian/Dockerfile @@ -0,0 +1,6 @@ +FROM node:24.12.0-trixie AS base +WORKDIR /app +COPY package.json . +COPY pnpm-lock.yaml . +RUN corepack enable pnpm && pnpm install --frozen-lockfile +CMD ["sleep", "60s"] \ No newline at end of file diff --git a/deployments/prebuilt-debian/package.json b/deployments/prebuilt-debian/package.json new file mode 100644 index 000000000..7cb8178cf --- /dev/null +++ b/deployments/prebuilt-debian/package.json @@ -0,0 +1,17 @@ +{ + "name": "homarr-prebuilt-debian", + "private": true, + "dependencies": { + "better-sqlite3": "^12.5.0" + }, + "packageManager": "pnpm@10.25.0", + "engines": { + "node": ">=24.12.0", + "pnpm": ">=10.25.0" + }, + "pnpm": { + "onlyBuiltDependencies": [ + "better-sqlite3" + ] + } +} diff --git a/deployments/prebuilt-debian/pnpm-lock.yaml b/deployments/prebuilt-debian/pnpm-lock.yaml new file mode 100644 index 000000000..1868baaa2 --- /dev/null +++ b/deployments/prebuilt-debian/pnpm-lock.yaml @@ -0,0 +1,286 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + better-sqlite3: + specifier: ^12.5.0 + version: 12.5.0 + +packages: + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + better-sqlite3@12.5.0: + resolution: {integrity: sha512-WwCZ/5Diz7rsF29o27o0Gcc1Du+l7Zsv7SYtVPG0X3G/uUI1LqdxrQI7c9Hs2FWpqXXERjW9hp6g3/tH7DlVKg==} + engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x} + + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + + node-abi@3.85.0: + resolution: {integrity: sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==} + engines: {node: '>=10'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + hasBin: true + + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + + tar-fs@2.1.4: + resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + +snapshots: + + base64-js@1.5.1: {} + + better-sqlite3@12.5.0: + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.3 + + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + chownr@1.1.4: {} + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + + deep-extend@0.6.0: {} + + detect-libc@2.1.2: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + expand-template@2.0.3: {} + + file-uri-to-path@1.0.0: {} + + fs-constants@1.0.0: {} + + github-from-package@0.0.0: {} + + ieee754@1.2.1: {} + + inherits@2.0.4: {} + + ini@1.3.8: {} + + mimic-response@3.1.0: {} + + minimist@1.2.8: {} + + mkdirp-classic@0.5.3: {} + + napi-build-utils@2.0.0: {} + + node-abi@3.85.0: + dependencies: + semver: 7.7.3 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + prebuild-install@7.1.3: + dependencies: + detect-libc: 2.1.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 2.0.0 + node-abi: 3.85.0 + pump: 3.0.3 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.4 + tunnel-agent: 0.6.0 + + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + safe-buffer@5.2.1: {} + + semver@7.7.3: {} + + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-json-comments@2.0.1: {} + + tar-fs@2.1.4: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.3 + tar-stream: 2.2.0 + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + + util-deprecate@1.0.2: {} + + wrappy@1.0.2: {} diff --git a/e2e/shared/e2e-db.ts b/e2e/shared/e2e-db.ts index 0abc87823..ea59efe6f 100644 --- a/e2e/shared/e2e-db.ts +++ b/e2e/shared/e2e-db.ts @@ -5,6 +5,7 @@ import Database from "better-sqlite3"; import { BetterSQLite3Database, drizzle } from "drizzle-orm/better-sqlite3"; import { migrate } from "drizzle-orm/better-sqlite3/migrator"; +import { DB_CASING } from "../../packages/core/src/infrastructure/db/constants"; import * as sqliteSchema from "../../packages/db/schema/sqlite"; export const createSqliteDbFileAsync = async () => { @@ -16,7 +17,7 @@ export const createSqliteDbFileAsync = async () => { const connection = new Database(localDbUrl); const db = drizzle(connection, { schema: sqliteSchema, - casing: "snake_case", + casing: DB_CASING, }); await migrate(db, { diff --git a/package.json b/package.json index 9ce1606db..abeaf6f8d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homarr", - "version": "1.45.3", + "version": "1.46.0", "private": true, "scripts": { "build": "cross-env CI=true turbo build", @@ -40,29 +40,29 @@ "@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/git": "^10.0.1", "@semantic-release/github": "^12.0.2", - "@semantic-release/npm": "^13.1.2", + "@semantic-release/npm": "^13.1.3", "@semantic-release/release-notes-generator": "^14.1.0", - "@testcontainers/redis": "^11.9.0", + "@testcontainers/redis": "^11.10.0", "@turbo/gen": "^2.6.3", - "@vitejs/plugin-react": "^5.1.1", + "@vitejs/plugin-react": "^5.1.2", "@vitest/coverage-v8": "^4.0.15", "@vitest/ui": "^4.0.15", "conventional-changelog-conventionalcommits": "^9.1.0", "cross-env": "^10.1.0", - "jsdom": "^27.2.0", + "jsdom": "^27.3.0", "json5": "^2.2.3", "prettier": "^3.7.4", "semantic-release": "^25.0.2", - "testcontainers": "^11.9.0", + "testcontainers": "^11.10.0", "turbo": "^2.6.3", "typescript": "^5.9.3", "vite-tsconfig-paths": "^5.1.4", "vitest": "^4.0.15" }, - "packageManager": "pnpm@10.24.0", + "packageManager": "pnpm@10.25.0", "engines": { - "node": ">=24.11.1", - "pnpm": ">=10.24.0" + "node": ">=24.12.0", + "pnpm": ">=10.25.0" }, "pnpm": { "onlyBuiltDependencies": [ @@ -84,18 +84,18 @@ "brace-expansion@>=1.0.0 <=1.1.11": ">=4.0.1", "esbuild@<=0.24.2": ">=0.27.1", "form-data@>=4.0.0 <4.0.4": ">=4.0.5", - "hono@<4.6.5": ">=4.10.7", + "hono@<4.6.5": ">=4.11.0", "linkifyjs@<4.3.2": ">=4.3.2", "nanoid@>=4.0.0 <5.0.9": ">=5.1.6", "prismjs@<1.30.0": ">=1.30.0", "proxmox-api>undici": "7.16.0", - "react-is": "^19.2.1", + "react-is": "^19.2.3", "rollup@>=4.0.0 <4.22.4": ">=4.53.3", "sha.js@<=2.4.11": ">=2.4.12", "tar-fs@>=3.0.0 <3.0.9": ">=3.1.1", "tar-fs@>=2.0.0 <2.1.3": ">=3.1.1", "tmp@<=0.2.3": ">=0.2.5", - "vite@>=5.0.0 <=5.4.18": ">=7.2.6" + "vite@>=5.0.0 <=5.4.18": ">=7.2.7" }, "patchedDependencies": { "@types/node-unifi": "patches/@types__node-unifi.patch", diff --git a/packages/analytics/package.json b/packages/analytics/package.json index 913f55d22..18324242c 100644 --- a/packages/analytics/package.json +++ b/packages/analytics/package.json @@ -22,8 +22,8 @@ }, "prettier": "@homarr/prettier-config", "dependencies": { + "@homarr/core": "workspace:^0.1.0", "@homarr/db": "workspace:^0.1.0", - "@homarr/log": "workspace:^0.1.0", "@homarr/server-settings": "workspace:^0.1.0", "@umami/node": "^0.4.0", "superjson": "2.2.6" @@ -32,7 +32,7 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/analytics/src/send-server-analytics.ts b/packages/analytics/src/send-server-analytics.ts index cdf94e134..27f1ae3d8 100644 --- a/packages/analytics/src/send-server-analytics.ts +++ b/packages/analytics/src/send-server-analytics.ts @@ -1,15 +1,17 @@ import type { UmamiEventData } from "@umami/node"; import { Umami } from "@umami/node"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { count, db } from "@homarr/db"; import { getServerSettingByKeyAsync } from "@homarr/db/queries"; import { integrations, items, users } from "@homarr/db/schema"; -import { logger } from "@homarr/log"; import type { defaultServerSettings } from "@homarr/server-settings"; import { Stopwatch } from "../../common/src"; import { UMAMI_HOST_URL, UMAMI_WEBSITE_ID } from "./constants"; +const logger = createLogger({ module: "analytics" }); + export const sendServerAnalyticsAsync = async () => { const stopWatch = new Stopwatch(); const analyticsSettings = await getServerSettingByKeyAsync(db, "analytics"); diff --git a/packages/api/package.json b/packages/api/package.json index 9709a64a0..cece7278f 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -22,7 +22,6 @@ "prettier": "@homarr/prettier-config", "dependencies": { "@homarr/auth": "workspace:^0.1.0", - "@homarr/certificates": "workspace:^0.1.0", "@homarr/common": "workspace:^0.1.0", "@homarr/core": "workspace:^0.1.0", "@homarr/cron-job-api": "workspace:^0.1.0", @@ -33,7 +32,6 @@ "@homarr/docker": "workspace:^0.1.0", "@homarr/icons": "workspace:^0.1.0", "@homarr/integrations": "workspace:^0.1.0", - "@homarr/log": "workspace:^", "@homarr/old-import": "workspace:^0.1.0", "@homarr/old-schema": "workspace:^0.1.0", "@homarr/ping": "workspace:^0.1.0", @@ -44,14 +42,14 @@ "@homarr/validation": "workspace:^0.1.0", "@kubernetes/client-node": "^1.4.0", "@tanstack/react-query": "^5.90.12", - "@trpc/client": "^11.7.2", - "@trpc/react-query": "^11.7.2", - "@trpc/server": "^11.7.2", - "@trpc/tanstack-react-query": "^11.7.2", + "@trpc/client": "^11.8.0", + "@trpc/react-query": "^11.8.0", + "@trpc/server": "^11.8.0", + "@trpc/tanstack-react-query": "^11.8.0", "lodash.clonedeep": "^4.5.0", - "next": "16.0.7", - "react": "19.2.1", - "react-dom": "19.2.1", + "next": "16.0.10", + "react": "19.2.3", + "react-dom": "19.2.3", "superjson": "2.2.6", "trpc-to-openapi": "^3.1.0", "zod": "^4.1.13" @@ -60,7 +58,7 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "prettier": "^3.7.4", "typescript": "^5.9.3" } diff --git a/packages/api/src/router/certificates/certificate-router.ts b/packages/api/src/router/certificates/certificate-router.ts index 4c7cfc862..cab6633f0 100644 --- a/packages/api/src/router/certificates/certificate-router.ts +++ b/packages/api/src/router/certificates/certificate-router.ts @@ -3,14 +3,19 @@ import { TRPCError } from "@trpc/server"; import { zfd } from "zod-form-data"; import { z } from "zod/v4"; -import { addCustomRootCertificateAsync, removeCustomRootCertificateAsync } from "@homarr/certificates/server"; +import { + addCustomRootCertificateAsync, + removeCustomRootCertificateAsync, +} from "@homarr/core/infrastructure/certificates"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { and, eq } from "@homarr/db"; import { trustedCertificateHostnames } from "@homarr/db/schema"; -import { logger } from "@homarr/log"; import { certificateValidFileNameSchema, checkCertificateFile } from "@homarr/validation/certificates"; import { createTRPCRouter, permissionRequiredProcedure } from "../../trpc"; +const logger = createLogger({ module: "certificateRouter" }); + export const certificateRouter = createTRPCRouter({ addCertificate: permissionRequiredProcedure .requiresPermission("admin") diff --git a/packages/api/src/router/cron-jobs.ts b/packages/api/src/router/cron-jobs.ts index 4564ec27a..5428ac374 100644 --- a/packages/api/src/router/cron-jobs.ts +++ b/packages/api/src/router/cron-jobs.ts @@ -1,14 +1,16 @@ import { observable } from "@trpc/server/observable"; import z from "zod/v4"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { cronExpressionSchema, jobGroupKeys, jobNameSchema } from "@homarr/cron-job-api"; import { cronJobApi } from "@homarr/cron-job-api/client"; import type { TaskStatus } from "@homarr/cron-job-status"; import { createCronJobStatusChannel } from "@homarr/cron-job-status"; -import { logger } from "@homarr/log"; import { createTRPCRouter, permissionRequiredProcedure } from "../trpc"; +const logger = createLogger({ module: "cronJobsRouter" }); + export const cronJobsRouter = createTRPCRouter({ triggerJob: permissionRequiredProcedure .requiresPermission("admin") diff --git a/packages/api/src/router/integration/integration-router.ts b/packages/api/src/router/integration/integration-router.ts index 23eca9fb5..4a12e062e 100644 --- a/packages/api/src/router/integration/integration-router.ts +++ b/packages/api/src/router/integration/integration-router.ts @@ -3,6 +3,7 @@ import { z } from "zod/v4"; import { createId, objectEntries } from "@homarr/common"; import { decryptSecret, encryptSecret } from "@homarr/common/server"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import type { Database } from "@homarr/db"; import { and, asc, eq, handleTransactionsAsync, inArray, like, or } from "@homarr/db"; import { @@ -26,7 +27,6 @@ import { integrationSecretKindObject, } from "@homarr/definitions"; import { createIntegrationAsync } from "@homarr/integrations"; -import { logger } from "@homarr/log"; import { byIdSchema } from "@homarr/validation/common"; import { integrationCreateSchema, @@ -40,6 +40,8 @@ import { throwIfActionForbiddenAsync } from "./integration-access"; import { MissingSecretError, testConnectionAsync } from "./integration-test-connection"; import { mapTestConnectionError } from "./map-test-connection-error"; +const logger = createLogger({ module: "integrationRouter" }); + export const integrationRouter = createTRPCRouter({ all: publicProcedure.query(async ({ ctx }) => { const groupsOfCurrentUser = await ctx.db.query.groupMembers.findMany({ diff --git a/packages/api/src/router/integration/integration-test-connection.ts b/packages/api/src/router/integration/integration-test-connection.ts index b2084b6a3..a1c735f25 100644 --- a/packages/api/src/router/integration/integration-test-connection.ts +++ b/packages/api/src/router/integration/integration-test-connection.ts @@ -1,9 +1,12 @@ import { decryptSecret } from "@homarr/common/server"; +import { createLogger } from "@homarr/core/infrastructure/logs"; +import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; import type { Integration } from "@homarr/db/schema"; import type { IntegrationKind, IntegrationSecretKind } from "@homarr/definitions"; import { getAllSecretKindOptions } from "@homarr/definitions"; import { createIntegrationAsync } from "@homarr/integrations"; -import { logger } from "@homarr/log"; + +const logger = createLogger({ module: "integrationTestConnection" }); type FormIntegration = Omit & { secrets: { @@ -35,8 +38,13 @@ export const testConnectionAsync = async ( }; } catch (error) { logger.warn( - new Error( - `Failed to decrypt secret from database integration="${integration.name}" secretKind="${secret.kind}"`, + new ErrorWithMetadata( + "Failed to decrypt secret from database", + { + integrationName: integration.name, + integrationKind: integration.kind, + secretKind: secret.kind, + }, { cause: error }, ), ); diff --git a/packages/api/src/router/kubernetes/router/cluster.ts b/packages/api/src/router/kubernetes/router/cluster.ts index e262ab8fa..adcb72389 100644 --- a/packages/api/src/router/kubernetes/router/cluster.ts +++ b/packages/api/src/router/kubernetes/router/cluster.ts @@ -2,7 +2,6 @@ import type { V1NodeList, VersionInfo } from "@kubernetes/client-node"; import { TRPCError } from "@trpc/server"; import type { ClusterResourceCount, KubernetesCluster } from "@homarr/definitions"; -import { logger } from "@homarr/log"; import { kubernetesMiddleware } from "../../../middlewares/kubernetes"; import { createTRPCRouter, permissionRequiredProcedure } from "../../../trpc"; @@ -129,7 +128,6 @@ export const clusterRouter = createTRPCRouter({ ], }; } catch (error) { - logger.error("Unable to retrieve cluster", error); throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "An error occurred while fetching Kubernetes cluster", @@ -165,7 +163,6 @@ export const clusterRouter = createTRPCRouter({ { label: "volumes", count: volumes.items.length }, ]; } catch (error) { - logger.error("Unable to retrieve cluster resource counts", error); throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "An error occurred while fetching Kubernetes resources count", diff --git a/packages/api/src/router/kubernetes/router/configMaps.ts b/packages/api/src/router/kubernetes/router/configMaps.ts index c97f6e44f..ac7edeeae 100644 --- a/packages/api/src/router/kubernetes/router/configMaps.ts +++ b/packages/api/src/router/kubernetes/router/configMaps.ts @@ -1,7 +1,6 @@ import { TRPCError } from "@trpc/server"; import type { KubernetesBaseResource } from "@homarr/definitions"; -import { logger } from "@homarr/log"; import { kubernetesMiddleware } from "../../../middlewares/kubernetes"; import { createTRPCRouter, permissionRequiredProcedure } from "../../../trpc"; @@ -25,7 +24,6 @@ export const configMapsRouter = createTRPCRouter({ }; }); } catch (error) { - logger.error("Unable to retrieve configMaps", error); throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "An error occurred while fetching Kubernetes ConfigMaps", diff --git a/packages/api/src/router/kubernetes/router/ingresses.ts b/packages/api/src/router/kubernetes/router/ingresses.ts index 17d6c97d3..a0ec41ce5 100644 --- a/packages/api/src/router/kubernetes/router/ingresses.ts +++ b/packages/api/src/router/kubernetes/router/ingresses.ts @@ -2,7 +2,6 @@ import type { V1HTTPIngressPath, V1Ingress, V1IngressRule } from "@kubernetes/cl import { TRPCError } from "@trpc/server"; import type { KubernetesIngress, KubernetesIngressPath, KubernetesIngressRuleAndPath } from "@homarr/definitions"; -import { logger } from "@homarr/log"; import { kubernetesMiddleware } from "../../../middlewares/kubernetes"; import { createTRPCRouter, permissionRequiredProcedure } from "../../../trpc"; @@ -43,7 +42,6 @@ export const ingressesRouter = createTRPCRouter({ return ingresses.items.map(mapIngress); } catch (error) { - logger.error("Unable to retrieve ingresses", error); throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "An error occurred while fetching Kubernetes ingresses", diff --git a/packages/api/src/router/kubernetes/router/namespaces.ts b/packages/api/src/router/kubernetes/router/namespaces.ts index 889075d4a..f515af890 100644 --- a/packages/api/src/router/kubernetes/router/namespaces.ts +++ b/packages/api/src/router/kubernetes/router/namespaces.ts @@ -1,7 +1,6 @@ import { TRPCError } from "@trpc/server"; import type { KubernetesNamespace, KubernetesNamespaceState } from "@homarr/definitions"; -import { logger } from "@homarr/log"; import { kubernetesMiddleware } from "../../../middlewares/kubernetes"; import { createTRPCRouter, permissionRequiredProcedure } from "../../../trpc"; @@ -25,7 +24,6 @@ export const namespacesRouter = createTRPCRouter({ } satisfies KubernetesNamespace; }); } catch (error) { - logger.error("Unable to retrieve namespaces", error); throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "An error occurred while fetching Kubernetes namespaces", diff --git a/packages/api/src/router/kubernetes/router/nodes.ts b/packages/api/src/router/kubernetes/router/nodes.ts index a4fc1959c..c7afe2a8b 100644 --- a/packages/api/src/router/kubernetes/router/nodes.ts +++ b/packages/api/src/router/kubernetes/router/nodes.ts @@ -1,7 +1,6 @@ import { TRPCError } from "@trpc/server"; import type { KubernetesNode, KubernetesNodeState } from "@homarr/definitions"; -import { logger } from "@homarr/log"; import { kubernetesMiddleware } from "../../../middlewares/kubernetes"; import { createTRPCRouter, permissionRequiredProcedure } from "../../../trpc"; @@ -57,7 +56,6 @@ export const nodesRouter = createTRPCRouter({ }; }); } catch (error) { - logger.error("Unable to retrieve nodes", error); throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "An error occurred while fetching Kubernetes nodes", diff --git a/packages/api/src/router/kubernetes/router/pods.ts b/packages/api/src/router/kubernetes/router/pods.ts index fbf5fb183..707e6ff58 100644 --- a/packages/api/src/router/kubernetes/router/pods.ts +++ b/packages/api/src/router/kubernetes/router/pods.ts @@ -2,13 +2,15 @@ import type { KubeConfig, V1OwnerReference } from "@kubernetes/client-node"; import { AppsV1Api } from "@kubernetes/client-node"; import { TRPCError } from "@trpc/server"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import type { KubernetesPod } from "@homarr/definitions"; -import { logger } from "@homarr/log"; import { kubernetesMiddleware } from "../../../middlewares/kubernetes"; import { createTRPCRouter, permissionRequiredProcedure } from "../../../trpc"; import { KubernetesClient } from "../kubernetes-client"; +const logger = createLogger({ module: "podsRouter" }); + export const podsRouter = createTRPCRouter({ getPods: permissionRequiredProcedure .requiresPermission("admin") @@ -55,7 +57,6 @@ export const podsRouter = createTRPCRouter({ return pods; } catch (error) { - logger.error("Unable to retrieve pods", error); throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "An error occurred while fetching Kubernetes pods", diff --git a/packages/api/src/router/kubernetes/router/secrets.ts b/packages/api/src/router/kubernetes/router/secrets.ts index 2fb80b272..2bf9b5665 100644 --- a/packages/api/src/router/kubernetes/router/secrets.ts +++ b/packages/api/src/router/kubernetes/router/secrets.ts @@ -1,7 +1,6 @@ import { TRPCError } from "@trpc/server"; import type { KubernetesSecret } from "@homarr/definitions"; -import { logger } from "@homarr/log"; import { kubernetesMiddleware } from "../../../middlewares/kubernetes"; import { createTRPCRouter, permissionRequiredProcedure } from "../../../trpc"; @@ -25,7 +24,6 @@ export const secretsRouter = createTRPCRouter({ }; }); } catch (error) { - logger.error("Unable to retrieve secrets", error); throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "An error occurred while fetching Kubernetes secrets", diff --git a/packages/api/src/router/kubernetes/router/services.ts b/packages/api/src/router/kubernetes/router/services.ts index 94b91598c..2d42cecfb 100644 --- a/packages/api/src/router/kubernetes/router/services.ts +++ b/packages/api/src/router/kubernetes/router/services.ts @@ -1,7 +1,6 @@ import { TRPCError } from "@trpc/server"; import type { KubernetesService } from "@homarr/definitions"; -import { logger } from "@homarr/log"; import { kubernetesMiddleware } from "../../../middlewares/kubernetes"; import { createTRPCRouter, permissionRequiredProcedure } from "../../../trpc"; @@ -29,7 +28,6 @@ export const servicesRouter = createTRPCRouter({ }; }); } catch (error) { - logger.error("Unable to retrieve services", error); throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "An error occurred while fetching Kubernetes services", diff --git a/packages/api/src/router/kubernetes/router/volumes.ts b/packages/api/src/router/kubernetes/router/volumes.ts index f1275a704..d9c824a68 100644 --- a/packages/api/src/router/kubernetes/router/volumes.ts +++ b/packages/api/src/router/kubernetes/router/volumes.ts @@ -1,7 +1,6 @@ import { TRPCError } from "@trpc/server"; import type { KubernetesVolume } from "@homarr/definitions"; -import { logger } from "@homarr/log"; import { kubernetesMiddleware } from "../../../middlewares/kubernetes"; import { createTRPCRouter, permissionRequiredProcedure } from "../../../trpc"; @@ -31,7 +30,6 @@ export const volumesRouter = createTRPCRouter({ }; }); } catch (error) { - logger.error("Unable to retrieve volumes", error); throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "An error occurred while fetching Kubernetes Volumes", diff --git a/packages/api/src/router/location.ts b/packages/api/src/router/location.ts index 7379078ed..72779d2e7 100644 --- a/packages/api/src/router/location.ts +++ b/packages/api/src/router/location.ts @@ -1,6 +1,6 @@ import { z } from "zod/v4"; -import { fetchWithTimeout } from "@homarr/common"; +import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout"; import { createTRPCRouter, publicProcedure } from "../trpc"; @@ -36,7 +36,7 @@ export const locationRouter = createTRPCRouter({ .input(locationSearchCityInput) .output(locationSearchCityOutput) .query(async ({ input }) => { - const res = await fetchWithTimeout(`https://geocoding-api.open-meteo.com/v1/search?name=${input.query}`); + const res = await fetchWithTimeoutAsync(`https://geocoding-api.open-meteo.com/v1/search?name=${input.query}`); return (await res.json()) as z.infer; }), }); diff --git a/packages/api/src/router/log.ts b/packages/api/src/router/log.ts index 315f51de4..10d44d0ab 100644 --- a/packages/api/src/router/log.ts +++ b/packages/api/src/router/log.ts @@ -1,14 +1,16 @@ import { observable } from "@trpc/server/observable"; import z from "zod/v4"; -import { logger } from "@homarr/log"; -import { logLevels } from "@homarr/log/constants"; +import { createLogger } from "@homarr/core/infrastructure/logs"; +import { logLevels } from "@homarr/core/infrastructure/logs/constants"; import type { LoggerMessage } from "@homarr/redis"; import { loggingChannel } from "@homarr/redis"; import { zodEnumFromArray } from "@homarr/validation/enums"; import { createTRPCRouter, permissionRequiredProcedure } from "../trpc"; +const logger = createLogger({ module: "logRouter" }); + export const logRouter = createTRPCRouter({ subscribe: permissionRequiredProcedure .requiresPermission("other-view-logs") diff --git a/packages/api/src/router/search-engine/search-engine-router.ts b/packages/api/src/router/search-engine/search-engine-router.ts index 6e983d38e..ddfc892a9 100644 --- a/packages/api/src/router/search-engine/search-engine-router.ts +++ b/packages/api/src/router/search-engine/search-engine-router.ts @@ -2,11 +2,11 @@ import { TRPCError } from "@trpc/server"; import { z } from "zod/v4"; import { createId } from "@homarr/common"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { asc, eq, like } from "@homarr/db"; import { getServerSettingByKeyAsync, updateServerSettingByKeyAsync } from "@homarr/db/queries"; import { searchEngines, users } from "@homarr/db/schema"; import { createIntegrationAsync } from "@homarr/integrations"; -import { logger } from "@homarr/log"; import { byIdSchema, paginatedSchema, searchSchema } from "@homarr/validation/common"; import { searchEngineEditSchema, searchEngineManageSchema } from "@homarr/validation/search-engine"; import { mediaRequestOptionsSchema, mediaRequestRequestSchema } from "@homarr/validation/widgets/media-request"; @@ -14,6 +14,8 @@ import { mediaRequestOptionsSchema, mediaRequestRequestSchema } from "@homarr/va import { createOneIntegrationMiddleware } from "../../middlewares/integration"; import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../../trpc"; +const logger = createLogger({ module: "searchEngineRouter" }); + export const searchEngineRouter = createTRPCRouter({ getPaginated: protectedProcedure.input(paginatedSchema).query(async ({ input, ctx }) => { const whereQuery = input.search ? like(searchEngines.name, `%${input.search.trim()}%`) : undefined; diff --git a/packages/api/src/router/update-checker.ts b/packages/api/src/router/update-checker.ts index 6920358ce..8957be5f2 100644 --- a/packages/api/src/router/update-checker.ts +++ b/packages/api/src/router/update-checker.ts @@ -1,8 +1,10 @@ -import { logger } from "@homarr/log"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { updateCheckerRequestHandler } from "@homarr/request-handler/update-checker"; import { createTRPCRouter, permissionRequiredProcedure } from "../trpc"; +const logger = createLogger({ module: "updateCheckerRouter" }); + export const updateCheckerRouter = createTRPCRouter({ getAvailableUpdates: permissionRequiredProcedure.requiresPermission("admin").query(async () => { try { diff --git a/packages/api/src/router/user.ts b/packages/api/src/router/user.ts index f59d6740e..875ffe80b 100644 --- a/packages/api/src/router/user.ts +++ b/packages/api/src/router/user.ts @@ -3,6 +3,7 @@ import { z } from "zod/v4"; import { createSaltAsync, hashPasswordAsync } from "@homarr/auth"; import { createId } from "@homarr/common"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import type { Database } from "@homarr/db"; import { and, eq, like } from "@homarr/db"; import { getMaxGroupPositionAsync } from "@homarr/db/queries"; @@ -10,7 +11,6 @@ import { boards, groupMembers, groupPermissions, groups, invites, users } from " import { selectUserSchema } from "@homarr/db/validationSchemas"; import { credentialsAdminGroup } from "@homarr/definitions"; import type { SupportedAuthProvider } from "@homarr/definitions"; -import { logger } from "@homarr/log"; import { byIdSchema } from "@homarr/validation/common"; import type { userBaseCreateSchema } from "@homarr/validation/user"; import { @@ -39,6 +39,8 @@ import { throwIfCredentialsDisabled } from "./invite/checks"; import { nextOnboardingStepAsync } from "./onboard/onboard-queries"; import { changeSearchPreferencesAsync, changeSearchPreferencesInputSchema } from "./user/change-search-preferences"; +const logger = createLogger({ module: "userRouter" }); + export const userRouter = createTRPCRouter({ initUser: onboardingProcedure .requiresStep("user") @@ -364,9 +366,11 @@ export const userRouter = createTRPCRouter({ // Admins can change the password of other users without providing the previous password const isPreviousPasswordRequired = ctx.session.user.id === input.userId; - logger.info( - `User ${user.id} is changing password for user ${input.userId}, previous password is required: ${isPreviousPasswordRequired}`, - ); + logger.info("Changing user password", { + actorId: ctx.session.user.id, + targetUserId: input.userId, + previousPasswordRequired: isPreviousPasswordRequired, + }); if (isPreviousPasswordRequired) { const previousPasswordHash = await hashPasswordAsync(input.previousPassword, dbUser.salt ?? ""); diff --git a/packages/api/src/router/widgets/indexer-manager.ts b/packages/api/src/router/widgets/indexer-manager.ts index b5b9ea6be..cb8b88262 100644 --- a/packages/api/src/router/widgets/indexer-manager.ts +++ b/packages/api/src/router/widgets/indexer-manager.ts @@ -4,7 +4,6 @@ import { observable } from "@trpc/server/observable"; import { getIntegrationKindsByCategory } from "@homarr/definitions"; import { createIntegrationAsync } from "@homarr/integrations"; import type { Indexer } from "@homarr/integrations/types"; -import { logger } from "@homarr/log"; import { indexerManagerRequestHandler } from "@homarr/request-handler/indexer-manager"; import type { IntegrationAction } from "../../middlewares/integration"; @@ -61,10 +60,10 @@ export const indexerManagerRouter = createTRPCRouter({ ctx.integrations.map(async (integration) => { const client = await createIntegrationAsync(integration); await client.testAllAsync().catch((err) => { - logger.error("indexer-manager router - ", err); throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: `Failed to test all indexers for ${integration.name} (${integration.id})`, + cause: err, }); }); }), diff --git a/packages/api/src/trpc.ts b/packages/api/src/trpc.ts index fd5f47442..bedd60f3d 100644 --- a/packages/api/src/trpc.ts +++ b/packages/api/src/trpc.ts @@ -14,12 +14,14 @@ import { ZodError } from "zod/v4"; import type { Session } from "@homarr/auth"; import { FlattenError } from "@homarr/common"; import { userAgent } from "@homarr/common/server"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { db } from "@homarr/db"; import type { GroupPermissionKey, OnboardingStep } from "@homarr/definitions"; -import { logger } from "@homarr/log"; import { getOnboardingOrFallbackAsync } from "./router/onboard/onboard-queries"; +const logger = createLogger({ module: "trpc" }); + /** * 1. CONTEXT * @@ -36,7 +38,7 @@ export const createTRPCContext = (opts: { headers: Headers; session: Session | n const session = opts.session; const source = opts.headers.get("x-trpc-source") ?? "unknown"; - logger.info(`tRPC request from ${source} by user '${session?.user.name} (${session?.user.id})'`, session?.user); + logger.info("Received tRPC request", { source, userId: session?.user.id, userName: session?.user.name }); return { session, diff --git a/packages/auth/configuration.ts b/packages/auth/configuration.ts index 177e7e8fb..b0181e326 100644 --- a/packages/auth/configuration.ts +++ b/packages/auth/configuration.ts @@ -3,9 +3,9 @@ import { cookies } from "next/headers"; import NextAuth from "next-auth"; import Credentials from "next-auth/providers/credentials"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { db } from "@homarr/db"; import type { SupportedAuthProvider } from "@homarr/definitions"; -import { logger } from "@homarr/log"; import { createAdapter } from "./adapter"; import { createSessionCallback } from "./callbacks"; @@ -18,6 +18,8 @@ import { OidcProvider } from "./providers/oidc/oidc-provider"; import { createRedirectUri } from "./redirect"; import { expireDateAfter, generateSessionToken, sessionTokenCookieName } from "./session"; +const logger = createLogger({ module: "authConfiguration" }); + // See why it's unknown in the [...nextauth]/route.ts file export const createConfiguration = ( provider: SupportedAuthProvider | "unknown", diff --git a/packages/auth/events.ts b/packages/auth/events.ts index b1222abb7..2593c418b 100644 --- a/packages/auth/events.ts +++ b/packages/auth/events.ts @@ -2,15 +2,17 @@ import { cookies } from "next/headers"; import dayjs from "dayjs"; import type { NextAuthConfig } from "next-auth"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { and, eq, inArray } from "@homarr/db"; import type { Database } from "@homarr/db"; import { groupMembers, groups, users } from "@homarr/db/schema"; import { colorSchemeCookieKey, everyoneGroup } from "@homarr/definitions"; -import { logger } from "@homarr/log"; import { env } from "./env"; import { extractProfileName } from "./providers/oidc/oidc-provider"; +const logger = createLogger({ module: "authEvents" }); + export const createSignInEventHandler = (db: Database): Exclude["signIn"] => { return async ({ user, profile }) => { logger.debug(`SignIn EventHandler for user: ${JSON.stringify(user)} . profile: ${JSON.stringify(profile)}`); @@ -43,9 +45,11 @@ export const createSignInEventHandler = (db: Database): Exclude 0) { - logger.debug( - `Homarr does not have the user in certain groups. user=${userId} count=${missingExternalGroupsForUser.length}`, - ); + logger.debug("Homarr does not have the user in certain groups.", { + user: userId, + count: missingExternalGroupsForUser.length, + }); const groupIds = await db.query.groups.findMany({ columns: { @@ -129,7 +138,10 @@ const synchronizeGroupsWithExternalForUserAsync = async (db: Database, userId: s where: inArray(groups.name, missingExternalGroupsForUser), }); - logger.debug(`Homarr has found groups in the database user is not in. user=${userId} count=${groupIds.length}`); + logger.debug("Homarr has found groups in the database user is not in.", { + user: userId, + count: groupIds.length, + }); if (groupIds.length > 0) { await db.insert(groupMembers).values( @@ -139,9 +151,9 @@ const synchronizeGroupsWithExternalForUserAsync = async (db: Database, userId: s })), ); - logger.info(`Added user to groups successfully. user=${userId} count=${groupIds.length}`); + logger.info("Added user to groups successfully.", { user: userId, count: groupIds.length }); } else { - logger.debug(`User is already in all groups of Homarr. user=${userId}`); + logger.debug("User is already in all groups of Homarr.", { user: userId }); } } @@ -154,9 +166,10 @@ const synchronizeGroupsWithExternalForUserAsync = async (db: Database, userId: s ); if (groupsUserIsNoLongerMemberOfExternally.length > 0) { - logger.debug( - `Homarr has the user in certain groups that LDAP does not have. user=${userId} count=${groupsUserIsNoLongerMemberOfExternally.length}`, - ); + logger.debug("Homarr has the user in certain groups that LDAP does not have.", { + user: userId, + count: groupsUserIsNoLongerMemberOfExternally.length, + }); await db.delete(groupMembers).where( and( @@ -168,8 +181,9 @@ const synchronizeGroupsWithExternalForUserAsync = async (db: Database, userId: s ), ); - logger.info( - `Removed user from groups successfully. user=${userId} count=${groupsUserIsNoLongerMemberOfExternally.length}`, - ); + logger.info("Removed user from groups successfully.", { + user: userId, + count: groupsUserIsNoLongerMemberOfExternally.length, + }); } }; diff --git a/packages/auth/package.json b/packages/auth/package.json index 99f92a04d..12049d28b 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -25,20 +25,18 @@ "dependencies": { "@auth/core": "^0.41.1", "@auth/drizzle-adapter": "^1.11.1", - "@homarr/certificates": "workspace:^0.1.0", "@homarr/common": "workspace:^0.1.0", "@homarr/core": "workspace:^0.1.0", "@homarr/db": "workspace:^0.1.0", "@homarr/definitions": "workspace:^0.1.0", - "@homarr/log": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0", "bcrypt": "^6.0.0", "cookies": "^0.9.1", - "ldapts": "8.0.12", - "next": "16.0.7", + "ldapts": "8.0.23", + "next": "16.0.10", "next-auth": "5.0.0-beta.30", - "react": "19.2.1", - "react-dom": "19.2.1", + "react": "19.2.3", + "react-dom": "19.2.3", "zod": "^4.1.13" }, "devDependencies": { @@ -47,7 +45,7 @@ "@homarr/tsconfig": "workspace:^0.1.0", "@types/bcrypt": "6.0.0", "@types/cookies": "0.9.2", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "prettier": "^3.7.4", "typescript": "^5.9.3" } diff --git a/packages/auth/providers/credentials/authorization/basic-authorization.ts b/packages/auth/providers/credentials/authorization/basic-authorization.ts index 544441a9d..04253d837 100644 --- a/packages/auth/providers/credentials/authorization/basic-authorization.ts +++ b/packages/auth/providers/credentials/authorization/basic-authorization.ts @@ -1,12 +1,14 @@ import bcrypt from "bcrypt"; import type { z } from "zod/v4"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import type { Database } from "@homarr/db"; import { and, eq } from "@homarr/db"; import { users } from "@homarr/db/schema"; -import { logger } from "@homarr/log"; import type { userSignInSchema } from "@homarr/validation/user"; +const logger = createLogger({ module: "basicAuthorization" }); + export const authorizeWithBasicCredentialsAsync = async ( db: Database, credentials: z.infer, @@ -16,19 +18,19 @@ export const authorizeWithBasicCredentialsAsync = async ( }); if (!user?.password) { - logger.info(`user ${credentials.name} was not found`); + logger.info("User not found", { userName: credentials.name }); return null; } - logger.info(`user ${user.name} is trying to log in. checking password...`); + logger.info("User is trying to log in. Checking password...", { userName: user.name }); const isValidPassword = await bcrypt.compare(credentials.password, user.password); if (!isValidPassword) { - logger.warn(`password for user ${user.name} was incorrect`); + logger.warn("Password for user was incorrect", { userName: user.name }); return null; } - logger.info(`user ${user.name} successfully authorized`); + logger.info("User successfully authorized", { userName: user.name }); return { id: user.id, diff --git a/packages/auth/providers/credentials/authorization/ldap-authorization.ts b/packages/auth/providers/credentials/authorization/ldap-authorization.ts index 22dd15181..592fde682 100644 --- a/packages/auth/providers/credentials/authorization/ldap-authorization.ts +++ b/packages/auth/providers/credentials/authorization/ldap-authorization.ts @@ -1,21 +1,23 @@ import { CredentialsSignin } from "@auth/core/errors"; import { z } from "zod/v4"; -import { createId, extractErrorMessage } from "@homarr/common"; +import { createId } from "@homarr/common"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import type { Database, InferInsertModel } from "@homarr/db"; import { and, eq } from "@homarr/db"; import { users } from "@homarr/db/schema"; -import { logger } from "@homarr/log"; import type { ldapSignInSchema } from "@homarr/validation/user"; import { env } from "../../../env"; import { LdapClient } from "../ldap-client"; +const logger = createLogger({ module: "ldapAuthorization" }); + export const authorizeWithLdapCredentialsAsync = async ( db: Database, credentials: z.infer, ) => { - logger.info(`user ${credentials.name} is trying to log in using LDAP. Connecting to LDAP server...`); + logger.info("User is trying to log in using LDAP. Connecting to LDAP server...", { userName: credentials.name }); const client = new LdapClient(); await client .bindAsync({ @@ -23,8 +25,7 @@ export const authorizeWithLdapCredentialsAsync = async ( password: env.AUTH_LDAP_BIND_PASSWORD, }) .catch((error) => { - logger.error(`Failed to connect to LDAP server ${extractErrorMessage(error)}`); - throw new CredentialsSignin(); + throw new CredentialsSignin("Failed to connect to LDAP server", { cause: error }); }); logger.info("Connected to LDAP server. Searching for user..."); @@ -48,21 +49,21 @@ export const authorizeWithLdapCredentialsAsync = async ( }); if (!ldapUser) { - logger.warn(`User ${credentials.name} not found in LDAP`); - throw new CredentialsSignin(); + throw new CredentialsSignin(`User not found in LDAP username="${credentials.name}"`); } // Validate email const mailResult = await z.string().email().safeParseAsync(ldapUser[env.AUTH_LDAP_USER_MAIL_ATTRIBUTE]); if (!mailResult.success) { - logger.error( - `User ${credentials.name} found but with invalid or non-existing Email. Not Supported: "${ldapUser[env.AUTH_LDAP_USER_MAIL_ATTRIBUTE]}"`, - ); - throw new CredentialsSignin(); + logger.error("User found in LDAP but with invalid or non-existing Email", { + userName: credentials.name, + emailValue: ldapUser[env.AUTH_LDAP_USER_MAIL_ATTRIBUTE], + }); + throw new CredentialsSignin("User found in LDAP but with invalid or non-existing Email"); } - logger.info(`User ${credentials.name} found in LDAP. Logging in...`); + logger.info("User found in LDAP. Logging in...", { userName: credentials.name }); // Bind with user credentials to check if the password is correct const userClient = new LdapClient(); @@ -72,12 +73,12 @@ export const authorizeWithLdapCredentialsAsync = async ( password: credentials.password, }) .catch(() => { - logger.warn(`Wrong credentials for user ${credentials.name}`); + logger.warn("Wrong credentials for user", { userName: credentials.name }); throw new CredentialsSignin(); }); await userClient.disconnectAsync(); - logger.info(`User ${credentials.name} logged in successfully, retrieving user groups...`); + logger.info("User credentials are correct. Retrieving user groups...", { userName: credentials.name }); const userGroups = await client .searchAsync({ @@ -93,7 +94,7 @@ export const authorizeWithLdapCredentialsAsync = async ( }) .then((entries) => entries.map((entry) => entry.cn).filter((group): group is string => group !== undefined)); - logger.info(`Found ${userGroups.length} groups for user ${credentials.name}.`); + logger.info("User groups retrieved", { userName: credentials.name, groups: userGroups.length }); await client.disconnectAsync(); @@ -111,7 +112,7 @@ export const authorizeWithLdapCredentialsAsync = async ( }); if (!user) { - logger.info(`User ${credentials.name} not found in the database. Creating...`); + logger.info("User not found in the database. Creating...", { userName: credentials.name }); const insertUser = { id: createId(), @@ -126,7 +127,7 @@ export const authorizeWithLdapCredentialsAsync = async ( user = insertUser; - logger.info(`User ${credentials.name} created successfully.`); + logger.info("User created successfully", { userName: credentials.name }); } return { diff --git a/packages/auth/providers/oidc/oidc-provider.ts b/packages/auth/providers/oidc/oidc-provider.ts index 7441b8f60..c706d978f 100644 --- a/packages/auth/providers/oidc/oidc-provider.ts +++ b/packages/auth/providers/oidc/oidc-provider.ts @@ -3,7 +3,7 @@ import type { OIDCConfig } from "@auth/core/providers"; import type { Profile } from "@auth/core/types"; import { customFetch } from "next-auth"; -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; import { env } from "../../env"; import { createRedirectUri } from "../../redirect"; diff --git a/packages/boards/package.json b/packages/boards/package.json index d0dd09152..d3a033f63 100644 --- a/packages/boards/package.json +++ b/packages/boards/package.json @@ -25,14 +25,14 @@ "prettier": "@homarr/prettier-config", "dependencies": { "@homarr/api": "workspace:^0.1.0", - "react": "19.2.1", - "react-dom": "19.2.1" + "react": "19.2.3", + "react-dom": "19.2.3" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/certificates/eslint.config.js b/packages/certificates/eslint.config.js deleted file mode 100644 index 5b19b6f8a..000000000 --- a/packages/certificates/eslint.config.js +++ /dev/null @@ -1,9 +0,0 @@ -import baseConfig from "@homarr/eslint-config/base"; - -/** @type {import('typescript-eslint').Config} */ -export default [ - { - ignores: [], - }, - ...baseConfig, -]; diff --git a/packages/certificates/package.json b/packages/certificates/package.json deleted file mode 100644 index c720ae98e..000000000 --- a/packages/certificates/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "@homarr/certificates", - "version": "0.1.0", - "private": true, - "license": "Apache-2.0", - "type": "module", - "exports": { - "./server": "./src/server.ts" - }, - "typesVersions": { - "*": { - "*": [ - "src/*" - ] - } - }, - "scripts": { - "clean": "rm -rf .turbo node_modules", - "format": "prettier --check . --ignore-path ../../.gitignore", - "lint": "eslint", - "typecheck": "tsc --noEmit" - }, - "prettier": "@homarr/prettier-config", - "dependencies": { - "@homarr/common": "workspace:^0.1.0", - "@homarr/db": "workspace:^0.1.0", - "undici": "7.16.0" - }, - "devDependencies": { - "@homarr/eslint-config": "workspace:^0.2.0", - "@homarr/prettier-config": "workspace:^0.1.0", - "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.39.1", - "typescript": "^5.9.3" - } -} diff --git a/packages/certificates/src/server.ts b/packages/certificates/src/server.ts deleted file mode 100644 index a138f4039..000000000 --- a/packages/certificates/src/server.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { X509Certificate } from "node:crypto"; -import fsSync from "node:fs"; -import fs from "node:fs/promises"; -import type { AgentOptions } from "node:https"; -import { Agent as HttpsAgent } from "node:https"; -import path from "node:path"; -import { checkServerIdentity, rootCertificates } from "node:tls"; -import axios from "axios"; -import type { RequestInfo, RequestInit, Response } from "undici"; -import { fetch } from "undici"; - -import { env } from "@homarr/common/env"; -import { LoggingAgent } from "@homarr/common/server"; -import type { InferSelectModel } from "@homarr/db"; -import { db } from "@homarr/db"; -import type { trustedCertificateHostnames } from "@homarr/db/schema"; - -const getCertificateFolder = () => { - if (env.NODE_ENV !== "production") return process.env.LOCAL_CERTIFICATE_PATH; - return process.env.LOCAL_CERTIFICATE_PATH ?? path.join("/appdata", "trusted-certificates"); -}; - -export const loadCustomRootCertificatesAsync = async () => { - const folder = getCertificateFolder(); - - if (!folder) { - return []; - } - - if (!fsSync.existsSync(folder)) { - await fs.mkdir(folder, { recursive: true }); - } - - const dirContent = await fs.readdir(folder); - return await Promise.all( - dirContent - .filter((file) => file.endsWith(".crt") || file.endsWith(".pem")) - .map(async (file) => ({ - content: await fs.readFile(path.join(folder, file), "utf8"), - fileName: file, - })), - ); -}; - -export const removeCustomRootCertificateAsync = async (fileName: string) => { - const folder = getCertificateFolder(); - if (!folder) { - return null; - } - - const existingFiles = await fs.readdir(folder, { withFileTypes: true }); - if (!existingFiles.some((file) => file.isFile() && file.name === fileName)) { - throw new Error(`File ${fileName} does not exist`); - } - - const fullPath = path.join(folder, fileName); - const content = await fs.readFile(fullPath, "utf8"); - - await fs.rm(fullPath); - try { - return new X509Certificate(content); - } catch { - return null; - } -}; - -export const addCustomRootCertificateAsync = async (fileName: string, content: string) => { - const folder = getCertificateFolder(); - if (!folder) { - throw new Error( - "When you want to use custom certificates locally you need to set LOCAL_CERTIFICATE_PATH to an absolute path", - ); - } - - if (fileName.includes("/")) { - throw new Error("Invalid file name"); - } - - await fs.writeFile(path.join(folder, fileName), content); -}; - -export const getTrustedCertificateHostnamesAsync = async () => { - return await db.query.trustedCertificateHostnames.findMany(); -}; - -export const getAllTrustedCertificatesAsync = async () => { - const customCertificates = await loadCustomRootCertificatesAsync(); - return rootCertificates.concat(customCertificates.map((cert) => cert.content)); -}; - -export const createCustomCheckServerIdentity = ( - trustedHostnames: InferSelectModel[], -): typeof checkServerIdentity => { - return (hostname, peerCertificate) => { - const matchingTrustedHostnames = trustedHostnames.filter( - (cert) => cert.thumbprint === peerCertificate.fingerprint256, - ); - - // We trust the certificate if we have a matching hostname - if (matchingTrustedHostnames.some((cert) => cert.hostname === hostname)) return undefined; - - return checkServerIdentity(hostname, peerCertificate); - }; -}; - -export const createCertificateAgentAsync = async (override?: { - ca: string | string[]; - checkServerIdentity: typeof checkServerIdentity; -}) => { - return new LoggingAgent({ - connect: override ?? { - ca: await getAllTrustedCertificatesAsync(), - checkServerIdentity: createCustomCheckServerIdentity(await getTrustedCertificateHostnamesAsync()), - }, - }); -}; - -export const createHttpsAgentAsync = async (override?: Pick) => { - return new HttpsAgent( - override ?? { - ca: await getAllTrustedCertificatesAsync(), - checkServerIdentity: createCustomCheckServerIdentity(await getTrustedCertificateHostnamesAsync()), - }, - ); -}; - -export const createAxiosCertificateInstanceAsync = async ( - override?: Pick, -) => { - return axios.create({ - httpsAgent: await createHttpsAgentAsync(override), - }); -}; - -export const fetchWithTrustedCertificatesAsync = async (url: RequestInfo, options?: RequestInit): Promise => { - const agent = await createCertificateAgentAsync(undefined); - return fetch(url, { - ...options, - dispatcher: agent, - }); -}; diff --git a/packages/certificates/tsconfig.json b/packages/certificates/tsconfig.json deleted file mode 100644 index cbe8483d9..000000000 --- a/packages/certificates/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "@homarr/tsconfig/base.json", - "compilerOptions": { - "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" - }, - "include": ["*.ts", "src"], - "exclude": ["node_modules"] -} diff --git a/packages/cli/package.json b/packages/cli/package.json index f50b73e31..5299843d0 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -35,7 +35,7 @@ "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", "esbuild": "^0.27.1", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/common/env.ts b/packages/common/env.ts index c52abbd25..cbccc8b70 100644 --- a/packages/common/env.ts +++ b/packages/common/env.ts @@ -24,12 +24,10 @@ export const env = createEnv({ message: `SECRET_ENCRYPTION_KEY must only contain hex characters${errorSuffix}`, }), NO_EXTERNAL_CONNECTION: createBooleanSchema(false), - ENABLE_DNS_CACHING: createBooleanSchema(false), }, runtimeEnv: { SECRET_ENCRYPTION_KEY: process.env.SECRET_ENCRYPTION_KEY, NODE_ENV: process.env.NODE_ENV, NO_EXTERNAL_CONNECTION: process.env.NO_EXTERNAL_CONNECTION, - ENABLE_DNS_CACHING: process.env.ENABLE_DNS_CACHING, }, }); diff --git a/packages/common/package.json b/packages/common/package.json index 772130855..fa5decbcc 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -8,7 +8,6 @@ ".": "./index.ts", "./types": "./src/types.ts", "./server": "./src/server.ts", - "./init-dns": "./src/dns.ts", "./client": "./src/client.ts", "./env": "./env.ts" }, @@ -28,14 +27,12 @@ "prettier": "@homarr/prettier-config", "dependencies": { "@homarr/core": "workspace:^0.1.0", - "@homarr/log": "workspace:^0.1.0", "@paralleldrive/cuid2": "^3.1.0", "dayjs": "^1.11.19", - "dns-caching": "^0.2.7", - "next": "16.0.7", + "next": "16.0.10", "octokit": "^5.0.5", - "react": "19.2.1", - "react-dom": "19.2.1", + "react": "19.2.3", + "react-dom": "19.2.3", "undici": "7.16.0", "zod": "^4.1.13", "zod-validation-error": "^5.0.0" @@ -44,7 +41,7 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/common/src/errors/http/handlers/axios-http-error-handler.ts b/packages/common/src/errors/http/handlers/axios-http-error-handler.ts index 471be713f..200d2cd0b 100644 --- a/packages/common/src/errors/http/handlers/axios-http-error-handler.ts +++ b/packages/common/src/errors/http/handlers/axios-http-error-handler.ts @@ -1,7 +1,5 @@ import { AxiosError } from "axios"; -import { logger } from "@homarr/log"; - import type { AnyRequestError } from "../request-error"; import { RequestError } from "../request-error"; import { ResponseError } from "../response-error"; @@ -9,11 +7,15 @@ import { matchErrorCode } from "./fetch-http-error-handler"; import { HttpErrorHandler } from "./http-error-handler"; export class AxiosHttpErrorHandler extends HttpErrorHandler { + constructor() { + super("axios"); + } + handleRequestError(error: unknown): AnyRequestError | undefined { if (!(error instanceof AxiosError)) return undefined; if (error.code === undefined) return undefined; - logger.debug("Received Axios request error", { + this.logRequestError({ code: error.code, message: error.message, }); @@ -28,8 +30,7 @@ export class AxiosHttpErrorHandler extends HttpErrorHandler { handleResponseError(error: unknown): ResponseError | undefined { if (!(error instanceof AxiosError)) return undefined; if (error.response === undefined) return undefined; - - logger.debug("Received Axios response error", { + this.logResponseError({ status: error.response.status, url: error.response.config.url, message: error.message, diff --git a/packages/common/src/errors/http/handlers/fetch-http-error-handler.ts b/packages/common/src/errors/http/handlers/fetch-http-error-handler.ts index db5d7b9fb..f9e3fb1cc 100644 --- a/packages/common/src/errors/http/handlers/fetch-http-error-handler.ts +++ b/packages/common/src/errors/http/handlers/fetch-http-error-handler.ts @@ -1,5 +1,3 @@ -import { logger } from "@homarr/log"; - import { objectEntries } from "../../../object"; import type { Modify } from "../../../types"; import type { AnyRequestError, AnyRequestErrorInput, RequestErrorCode, RequestErrorReason } from "../request-error"; @@ -9,13 +7,13 @@ import { HttpErrorHandler } from "./http-error-handler"; export class FetchHttpErrorHandler extends HttpErrorHandler { constructor(private type = "undici") { - super(); + super(type); } handleRequestError(error: unknown): AnyRequestError | undefined { if (!isTypeErrorWithCode(error)) return undefined; - logger.debug(`Received ${this.type} request error`, { + this.logRequestError({ code: error.cause.code, }); diff --git a/packages/common/src/errors/http/handlers/http-error-handler.ts b/packages/common/src/errors/http/handlers/http-error-handler.ts index 4195e0dd5..4a661199f 100644 --- a/packages/common/src/errors/http/handlers/http-error-handler.ts +++ b/packages/common/src/errors/http/handlers/http-error-handler.ts @@ -1,7 +1,24 @@ +import type { ILogger } from "@homarr/core/infrastructure/logs"; +import { createLogger } from "@homarr/core/infrastructure/logs"; + import type { AnyRequestError } from "../request-error"; import type { ResponseError } from "../response-error"; export abstract class HttpErrorHandler { + protected logger: ILogger; + + constructor(type: string) { + this.logger = createLogger({ module: "httpErrorHandler", type }); + } + + protected logRequestError(metadata: T) { + this.logger.debug("Received request error", metadata); + } + + protected logResponseError(metadata: T) { + this.logger.debug("Received response error", metadata); + } + abstract handleRequestError(error: unknown): AnyRequestError | undefined; abstract handleResponseError(error: unknown): ResponseError | undefined; } diff --git a/packages/common/src/errors/http/handlers/node-fetch-http-error-handler.ts b/packages/common/src/errors/http/handlers/node-fetch-http-error-handler.ts index facfc5a91..4f70395de 100644 --- a/packages/common/src/errors/http/handlers/node-fetch-http-error-handler.ts +++ b/packages/common/src/errors/http/handlers/node-fetch-http-error-handler.ts @@ -1,7 +1,5 @@ import { FetchError } from "node-fetch"; -import { logger } from "@homarr/log"; - import { RequestError } from "../request-error"; import type { AnyRequestError } from "../request-error"; import type { ResponseError } from "../response-error"; @@ -15,14 +13,14 @@ import { HttpErrorHandler } from "./http-error-handler"; */ export class NodeFetchHttpErrorHandler extends HttpErrorHandler { constructor(private type = "node-fetch") { - super(); + super(type); } handleRequestError(error: unknown): AnyRequestError | undefined { if (!(error instanceof FetchError)) return undefined; if (error.code === undefined) return undefined; - logger.debug(`Received ${this.type} request error`, { + this.logRequestError({ code: error.code, message: error.message, }); diff --git a/packages/common/src/errors/http/handlers/octokit-http-error-handler.ts b/packages/common/src/errors/http/handlers/octokit-http-error-handler.ts index 3298770c2..6f8bb0478 100644 --- a/packages/common/src/errors/http/handlers/octokit-http-error-handler.ts +++ b/packages/common/src/errors/http/handlers/octokit-http-error-handler.ts @@ -5,6 +5,10 @@ import { ResponseError } from "../response-error"; import { HttpErrorHandler } from "./http-error-handler"; export class OctokitHttpErrorHandler extends HttpErrorHandler { + constructor() { + super("octokit"); + } + /** * I wasn't able to get a request error triggered. Therefore we ignore them for now * and just forward them as unknown errors @@ -16,6 +20,11 @@ export class OctokitHttpErrorHandler extends HttpErrorHandler { handleResponseError(error: unknown): ResponseError | undefined { if (!(error instanceof OctokitRequestError)) return undefined; + this.logResponseError({ + status: error.status, + url: error.response?.url, + }); + return new ResponseError({ status: error.status, url: error.response?.url, diff --git a/packages/common/src/errors/http/handlers/ofetch-http-error-handler.ts b/packages/common/src/errors/http/handlers/ofetch-http-error-handler.ts index 22d79ad2c..bc347e5a8 100644 --- a/packages/common/src/errors/http/handlers/ofetch-http-error-handler.ts +++ b/packages/common/src/errors/http/handlers/ofetch-http-error-handler.ts @@ -1,7 +1,5 @@ import { FetchError } from "ofetch"; -import { logger } from "@homarr/log"; - import type { AnyRequestError } from "../request-error"; import { ResponseError } from "../response-error"; import { FetchHttpErrorHandler } from "./fetch-http-error-handler"; @@ -14,6 +12,10 @@ import { HttpErrorHandler } from "./http-error-handler"; * It is for example used within the ctrl packages like qbittorrent, deluge, transmission, etc. */ export class OFetchHttpErrorHandler extends HttpErrorHandler { + constructor() { + super("ofetch"); + } + handleRequestError(error: unknown): AnyRequestError | undefined { if (!(error instanceof FetchError)) return undefined; if (!(error.cause instanceof TypeError)) return undefined; @@ -28,7 +30,7 @@ export class OFetchHttpErrorHandler extends HttpErrorHandler { if (!(error instanceof FetchError)) return undefined; if (error.response === undefined) return undefined; - logger.debug("Received ofetch response error", { + this.logResponseError({ status: error.response.status, url: error.response.url, }); diff --git a/packages/common/src/errors/http/handlers/tsdav-http-error-handler.ts b/packages/common/src/errors/http/handlers/tsdav-http-error-handler.ts index 76817ce0f..6fb12c759 100644 --- a/packages/common/src/errors/http/handlers/tsdav-http-error-handler.ts +++ b/packages/common/src/errors/http/handlers/tsdav-http-error-handler.ts @@ -1,11 +1,13 @@ -import { logger } from "@homarr/log"; - import type { AnyRequestError } from "../request-error"; import { ResponseError } from "../response-error"; import { HttpErrorHandler } from "./http-error-handler"; import { NodeFetchHttpErrorHandler } from "./node-fetch-http-error-handler"; export class TsdavHttpErrorHandler extends HttpErrorHandler { + constructor() { + super("tsdav"); + } + handleRequestError(error: unknown): AnyRequestError | undefined { return new NodeFetchHttpErrorHandler("tsdav").handleRequestError(error); } @@ -16,8 +18,9 @@ export class TsdavHttpErrorHandler extends HttpErrorHandler { // https://github.com/natelindev/tsdav/blob/bf33f04b1884694d685ee6f2b43fe9354b12d167/src/account.ts#L86 if (error.message !== "Invalid credentials") return undefined; - logger.debug("Received tsdav response error", { + this.logResponseError({ status: 401, + url: undefined, }); return new ResponseError({ status: 401, url: "?" }); diff --git a/packages/common/src/errors/parse/handlers/json-parse-error-handler.ts b/packages/common/src/errors/parse/handlers/json-parse-error-handler.ts index b0b5581a8..e8bb25ec6 100644 --- a/packages/common/src/errors/parse/handlers/json-parse-error-handler.ts +++ b/packages/common/src/errors/parse/handlers/json-parse-error-handler.ts @@ -1,13 +1,15 @@ -import { logger } from "@homarr/log"; - import { ParseError } from "../parse-error"; import { ParseErrorHandler } from "./parse-error-handler"; export class JsonParseErrorHandler extends ParseErrorHandler { + constructor() { + super("json"); + } + handleParseError(error: unknown): ParseError | undefined { if (!(error instanceof SyntaxError)) return undefined; - logger.debug("Received JSON parse error", { + this.logParseError({ message: error.message, }); diff --git a/packages/common/src/errors/parse/handlers/parse-error-handler.ts b/packages/common/src/errors/parse/handlers/parse-error-handler.ts index 7b19b579d..428eb36c0 100644 --- a/packages/common/src/errors/parse/handlers/parse-error-handler.ts +++ b/packages/common/src/errors/parse/handlers/parse-error-handler.ts @@ -1,5 +1,17 @@ +import type { ILogger } from "@homarr/core/infrastructure/logs"; +import { createLogger } from "@homarr/core/infrastructure/logs"; + import type { ParseError } from "../parse-error"; export abstract class ParseErrorHandler { + protected logger: ILogger; + constructor(type: string) { + this.logger = createLogger({ module: "parseErrorHandler", type }); + } + + protected logParseError(metadata?: Record) { + this.logger.debug("Received parse error", metadata); + } + abstract handleParseError(error: unknown): ParseError | undefined; } diff --git a/packages/common/src/errors/parse/handlers/zod-parse-error-handler.ts b/packages/common/src/errors/parse/handlers/zod-parse-error-handler.ts index 9f581ba99..2146ebb02 100644 --- a/packages/common/src/errors/parse/handlers/zod-parse-error-handler.ts +++ b/packages/common/src/errors/parse/handlers/zod-parse-error-handler.ts @@ -1,12 +1,14 @@ import { fromError } from "zod-validation-error"; import { ZodError } from "zod/v4"; -import { logger } from "@homarr/log"; - import { ParseError } from "../parse-error"; import { ParseErrorHandler } from "./parse-error-handler"; export class ZodParseErrorHandler extends ParseErrorHandler { + constructor() { + super("zod"); + } + handleParseError(error: unknown): ParseError | undefined { if (!(error instanceof ZodError)) return undefined; @@ -17,7 +19,7 @@ export class ZodParseErrorHandler extends ParseErrorHandler { prefix: null, }).toString(); - logger.debug("Received Zod parse error"); + this.logParseError(); return new ParseError(message, { cause: error }); } diff --git a/packages/common/src/fetch-agent.ts b/packages/common/src/fetch-agent.ts deleted file mode 100644 index ca670cd36..000000000 --- a/packages/common/src/fetch-agent.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { Dispatcher } from "undici"; -import { Agent } from "undici"; - -import { logger } from "@homarr/log"; - -// The below import statement initializes dns-caching -import "./dns"; - -export class LoggingAgent extends Agent { - constructor(...props: ConstructorParameters) { - super(...props); - } - - dispatch(options: Dispatcher.DispatchOptions, handler: Dispatcher.DispatchHandler): boolean { - const path = options.path - .split("/") - .map((segment) => (segment.length >= 32 && !segment.startsWith("?") ? "REDACTED" : segment)) - .join("/"); - const url = new URL(`${options.origin as string}${path}`); - - // The below code should prevent sensitive data from being logged as - // some integrations use query parameters for auth - url.searchParams.forEach((value, key) => { - if (value === "") return; // Skip empty values - if (/^-?\d{1,12}$/.test(value)) return; // Skip small numbers - if (value === "true" || value === "false") return; // Skip boolean values - if (/^[a-zA-Z]{1,12}$/.test(value)) return; // Skip short strings - if (/^\d{4}-\d{2}-\d{2}$/.test(value)) return; // Skip dates - if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value)) return; // Skip date times - - url.searchParams.set(key, "REDACTED"); - }); - - logger.debug( - `Dispatching request ${url.toString().replaceAll("=&", "&")} (${Object.keys(options.headers ?? {}).length} headers)`, - ); - return super.dispatch(options, handler); - } -} diff --git a/packages/common/src/fetch-with-timeout.ts b/packages/common/src/fetch-with-timeout.ts deleted file mode 100644 index 48353415f..000000000 --- a/packages/common/src/fetch-with-timeout.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Same as fetch, but with a timeout of 10 seconds. - * https://stackoverflow.com/questions/46946380/fetch-api-request-timeout - * @param param0 fetch arguments - * @returns fetch response - */ -export const fetchWithTimeout = (...[url, requestInit]: Parameters) => { - const controller = new AbortController(); - - // 10 seconds timeout: - const timeoutId = setTimeout(() => controller.abort(), 10000); - - return fetch(url, { signal: controller.signal, ...requestInit }).finally(() => { - clearTimeout(timeoutId); - }); -}; diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index f7d667155..5f82ea6a3 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -9,7 +9,6 @@ export * from "./id"; export * from "./url"; export * from "./number"; export * from "./error"; -export * from "./fetch-with-timeout"; export * from "./theme"; export * from "./function"; export * from "./id"; diff --git a/packages/common/src/server.ts b/packages/common/src/server.ts index 5d0a7de4f..fd23f7404 100644 --- a/packages/common/src/server.ts +++ b/packages/common/src/server.ts @@ -1,5 +1,4 @@ export * from "./security"; export * from "./encryption"; export * from "./user-agent"; -export * from "./fetch-agent"; export * from "./errors"; diff --git a/packages/core/package.json b/packages/core/package.json index aced00b5b..6fe412842 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -7,7 +7,20 @@ "exports": { "./infrastructure/redis": "./src/infrastructure/redis/client.ts", "./infrastructure/env": "./src/infrastructure/env/index.ts", - ".": "./src/index.ts" + "./infrastructure/logs": "./src/infrastructure/logs/index.ts", + "./infrastructure/logs/constants": "./src/infrastructure/logs/constants.ts", + "./infrastructure/logs/env": "./src/infrastructure/logs/env.ts", + "./infrastructure/logs/error": "./src/infrastructure/logs/error.ts", + "./infrastructure/db": "./src/infrastructure/db/index.ts", + "./infrastructure/db/env": "./src/infrastructure/db/env.ts", + "./infrastructure/db/constants": "./src/infrastructure/db/constants.ts", + "./infrastructure/certificates": "./src/infrastructure/certificates/index.ts", + "./infrastructure/certificates/hostnames/db/sqlite": "./src/infrastructure/certificates/hostnames/db/sqlite.ts", + "./infrastructure/certificates/hostnames/db/mysql": "./src/infrastructure/certificates/hostnames/db/mysql.ts", + "./infrastructure/certificates/hostnames/db/postgresql": "./src/infrastructure/certificates/hostnames/db/postgresql.ts", + "./infrastructure/dns/init": "./src/infrastructure/dns/init.ts", + "./infrastructure/http": "./src/infrastructure/http/index.ts", + "./infrastructure/http/timeout": "./src/infrastructure/http/timeout.ts" }, "typesVersions": { "*": { @@ -25,14 +38,23 @@ "prettier": "@homarr/prettier-config", "dependencies": { "@t3-oss/env-nextjs": "^0.13.8", + "better-sqlite3": "^12.5.0", + "dns-caching": "^0.2.9", + "drizzle-orm": "^0.45.1", "ioredis": "5.8.2", + "mysql2": "3.15.3", + "pg": "^8.16.3", + "superjson": "2.2.6", + "winston": "3.19.0", "zod": "^4.1.13" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.39.1", + "@types/better-sqlite3": "7.6.13", + "@types/pg": "^8.16.0", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/core/src/infrastructure/certificates/files/index.ts b/packages/core/src/infrastructure/certificates/files/index.ts new file mode 100644 index 000000000..fcc058ad9 --- /dev/null +++ b/packages/core/src/infrastructure/certificates/files/index.ts @@ -0,0 +1,74 @@ +import { X509Certificate } from "node:crypto"; +import fsSync from "node:fs"; +import fs from "node:fs/promises"; +import path from "node:path"; +import { rootCertificates } from "node:tls"; + +const getCertificateFolder = () => { + if (process.env.NODE_ENV !== "production") return process.env.LOCAL_CERTIFICATE_PATH; + return process.env.LOCAL_CERTIFICATE_PATH ?? path.join("/appdata", "trusted-certificates"); +}; + +export const loadCustomRootCertificatesAsync = async () => { + const folder = getCertificateFolder(); + + if (!folder) { + return []; + } + + if (!fsSync.existsSync(folder)) { + await fs.mkdir(folder, { recursive: true }); + } + + const dirContent = await fs.readdir(folder); + return await Promise.all( + dirContent + .filter((file) => file.endsWith(".crt") || file.endsWith(".pem")) + .map(async (file) => ({ + content: await fs.readFile(path.join(folder, file), "utf8"), + fileName: file, + })), + ); +}; + +export const getAllTrustedCertificatesAsync = async () => { + const customCertificates = await loadCustomRootCertificatesAsync(); + return rootCertificates.concat(customCertificates.map((cert) => cert.content)); +}; + +export const removeCustomRootCertificateAsync = async (fileName: string) => { + const folder = getCertificateFolder(); + if (!folder) { + return null; + } + + const existingFiles = await fs.readdir(folder, { withFileTypes: true }); + if (!existingFiles.some((file) => file.isFile() && file.name === fileName)) { + throw new Error(`File ${fileName} does not exist`); + } + + const fullPath = path.join(folder, fileName); + const content = await fs.readFile(fullPath, "utf8"); + + await fs.rm(fullPath); + try { + return new X509Certificate(content); + } catch { + return null; + } +}; + +export const addCustomRootCertificateAsync = async (fileName: string, content: string) => { + const folder = getCertificateFolder(); + if (!folder) { + throw new Error( + "When you want to use custom certificates locally you need to set LOCAL_CERTIFICATE_PATH to an absolute path", + ); + } + + if (fileName.includes("/")) { + throw new Error("Invalid file name"); + } + + await fs.writeFile(path.join(folder, fileName), content); +}; diff --git a/packages/core/src/infrastructure/certificates/hostnames/db/mysql.ts b/packages/core/src/infrastructure/certificates/hostnames/db/mysql.ts new file mode 100644 index 000000000..01dde026d --- /dev/null +++ b/packages/core/src/infrastructure/certificates/hostnames/db/mysql.ts @@ -0,0 +1,15 @@ +import { mysqlTable, primaryKey, text, varchar } from "drizzle-orm/mysql-core"; + +export const trustedCertificateHostnames = mysqlTable( + "trusted_certificate_hostname", + { + hostname: varchar({ length: 256 }).notNull(), + thumbprint: varchar({ length: 128 }).notNull(), + certificate: text().notNull(), + }, + (table) => ({ + compoundKey: primaryKey({ + columns: [table.hostname, table.thumbprint], + }), + }), +); diff --git a/packages/core/src/infrastructure/certificates/hostnames/db/postgresql.ts b/packages/core/src/infrastructure/certificates/hostnames/db/postgresql.ts new file mode 100644 index 000000000..6e89bd392 --- /dev/null +++ b/packages/core/src/infrastructure/certificates/hostnames/db/postgresql.ts @@ -0,0 +1,15 @@ +import { pgTable, primaryKey, text, varchar } from "drizzle-orm/pg-core"; + +export const trustedCertificateHostnames = pgTable( + "trusted_certificate_hostname", + { + hostname: varchar({ length: 256 }).notNull(), + thumbprint: varchar({ length: 128 }).notNull(), + certificate: text().notNull(), + }, + (table) => ({ + compoundKey: primaryKey({ + columns: [table.hostname, table.thumbprint], + }), + }), +); diff --git a/packages/core/src/infrastructure/certificates/hostnames/db/schema.ts b/packages/core/src/infrastructure/certificates/hostnames/db/schema.ts new file mode 100644 index 000000000..4bea4a61b --- /dev/null +++ b/packages/core/src/infrastructure/certificates/hostnames/db/schema.ts @@ -0,0 +1,10 @@ +import { createSchema } from "../../../db"; +import * as mysql from "./mysql"; +import * as postgresql from "./postgresql"; +import * as sqlite from "./sqlite"; + +export const schema = createSchema({ + "better-sqlite3": () => sqlite, + mysql2: () => mysql, + "node-postgres": () => postgresql, +}); diff --git a/packages/core/src/infrastructure/certificates/hostnames/db/sqlite.ts b/packages/core/src/infrastructure/certificates/hostnames/db/sqlite.ts new file mode 100644 index 000000000..6ad036139 --- /dev/null +++ b/packages/core/src/infrastructure/certificates/hostnames/db/sqlite.ts @@ -0,0 +1,15 @@ +import { primaryKey, sqliteTable, text } from "drizzle-orm/sqlite-core"; + +export const trustedCertificateHostnames = sqliteTable( + "trusted_certificate_hostname", + { + hostname: text().notNull(), + thumbprint: text().notNull(), + certificate: text().notNull(), + }, + (table) => ({ + compoundKey: primaryKey({ + columns: [table.hostname, table.thumbprint], + }), + }), +); diff --git a/packages/core/src/infrastructure/certificates/hostnames/index.ts b/packages/core/src/infrastructure/certificates/hostnames/index.ts new file mode 100644 index 000000000..afcbf11dd --- /dev/null +++ b/packages/core/src/infrastructure/certificates/hostnames/index.ts @@ -0,0 +1,12 @@ +import type { InferSelectModel } from "drizzle-orm"; + +import { createDb } from "../../db"; +import { schema } from "./db/schema"; + +const db = createDb(schema); + +export const getTrustedCertificateHostnamesAsync = async () => { + return await db.query.trustedCertificateHostnames.findMany(); +}; + +export type TrustedCertificateHostname = InferSelectModel; diff --git a/packages/core/src/infrastructure/certificates/index.ts b/packages/core/src/infrastructure/certificates/index.ts new file mode 100644 index 000000000..fb4ecaa0b --- /dev/null +++ b/packages/core/src/infrastructure/certificates/index.ts @@ -0,0 +1,7 @@ +export { getTrustedCertificateHostnamesAsync } from "./hostnames"; +export { + addCustomRootCertificateAsync, + removeCustomRootCertificateAsync, + getAllTrustedCertificatesAsync, + loadCustomRootCertificatesAsync, +} from "./files"; diff --git a/packages/core/src/infrastructure/db/constants.ts b/packages/core/src/infrastructure/db/constants.ts new file mode 100644 index 000000000..264303126 --- /dev/null +++ b/packages/core/src/infrastructure/db/constants.ts @@ -0,0 +1,3 @@ +import type { Casing } from "drizzle-orm"; + +export const DB_CASING: Casing = "snake_case"; diff --git a/packages/core/src/infrastructure/db/drivers/index.ts b/packages/core/src/infrastructure/db/drivers/index.ts new file mode 100644 index 000000000..0c5449c7e --- /dev/null +++ b/packages/core/src/infrastructure/db/drivers/index.ts @@ -0,0 +1,27 @@ +import { DB_CASING } from "../constants"; +import { createDbMapping } from "../mapping"; +import { createMysqlDb } from "./mysql"; +import { createPostgresDb } from "./postgresql"; +import type { SharedDrizzleConfig } from "./shared"; +import { WinstonDrizzleLogger } from "./shared"; +import { createSqliteDb } from "./sqlite"; + +export type Database> = ReturnType>; + +export const createSharedConfig = >( + schema: TSchema, +): SharedDrizzleConfig => ({ + logger: new WinstonDrizzleLogger(), + casing: DB_CASING, + schema, +}); + +export const createDb = >(schema: TSchema) => { + const config = createSharedConfig(schema); + + return createDbMapping({ + mysql2: () => createMysqlDb(config), + "node-postgres": () => createPostgresDb(config), + "better-sqlite3": () => createSqliteDb(config), + }); +}; diff --git a/packages/core/src/infrastructure/db/drivers/mysql.ts b/packages/core/src/infrastructure/db/drivers/mysql.ts new file mode 100644 index 000000000..d34c4f51b --- /dev/null +++ b/packages/core/src/infrastructure/db/drivers/mysql.ts @@ -0,0 +1,33 @@ +import { drizzle } from "drizzle-orm/mysql2"; +import mysql from "mysql2"; +import type { PoolOptions } from "mysql2"; + +import { dbEnv } from "../env"; +import type { SharedDrizzleConfig } from "./shared"; + +export const createMysqlDb = >(config: SharedDrizzleConfig) => { + const connection = createMysqlDbConnection(); + return drizzle(connection, { + ...config, + mode: "default", + }); +}; + +const createMysqlDbConnection = () => { + const defaultOptions = { + maxIdle: 0, + idleTimeout: 60000, + enableKeepAlive: true, + } satisfies Partial; + + if (!dbEnv.HOST) { + return mysql.createPool({ ...defaultOptions, uri: dbEnv.URL }); + } + + return mysql.createPool({ + ...defaultOptions, + port: dbEnv.PORT, + user: dbEnv.USER, + password: dbEnv.PASSWORD, + }); +}; diff --git a/packages/core/src/infrastructure/db/drivers/postgresql.ts b/packages/core/src/infrastructure/db/drivers/postgresql.ts new file mode 100644 index 000000000..764b1641c --- /dev/null +++ b/packages/core/src/infrastructure/db/drivers/postgresql.ts @@ -0,0 +1,38 @@ +import { drizzle as drizzlePostgres } from "drizzle-orm/node-postgres"; +import type { PoolOptions as PostgresPoolOptions } from "pg"; +import { Pool as PostgresPool } from "pg"; + +import { dbEnv } from "../env"; +import type { SharedDrizzleConfig } from "./shared"; + +export const createPostgresDb = >(config: SharedDrizzleConfig) => { + const connection = createPostgresDbConnection(); + return drizzlePostgres({ + ...config, + client: connection, + }); +}; + +const createPostgresDbConnection = () => { + const defaultOptions = { + max: 0, + idleTimeoutMillis: 60000, + allowExitOnIdle: false, + } satisfies Partial; + + if (!dbEnv.HOST) { + return new PostgresPool({ + ...defaultOptions, + connectionString: dbEnv.URL, + }); + } + + return new PostgresPool({ + ...defaultOptions, + host: dbEnv.HOST, + port: dbEnv.PORT, + database: dbEnv.NAME, + user: dbEnv.USER, + password: dbEnv.PASSWORD, + }); +}; diff --git a/packages/core/src/infrastructure/db/drivers/shared.ts b/packages/core/src/infrastructure/db/drivers/shared.ts new file mode 100644 index 000000000..90d5470bd --- /dev/null +++ b/packages/core/src/infrastructure/db/drivers/shared.ts @@ -0,0 +1,15 @@ +import type { DrizzleConfig, Logger } from "drizzle-orm"; + +import { createLogger } from "../../logs"; + +export type SharedDrizzleConfig> = Required< + Pick, "logger" | "casing" | "schema"> +>; + +const logger = createLogger({ module: "db" }); + +export class WinstonDrizzleLogger implements Logger { + logQuery(query: string, _: unknown[]): void { + logger.debug("Executed SQL query", { query }); + } +} diff --git a/packages/core/src/infrastructure/db/drivers/sqlite.ts b/packages/core/src/infrastructure/db/drivers/sqlite.ts new file mode 100644 index 000000000..ce95fe5a0 --- /dev/null +++ b/packages/core/src/infrastructure/db/drivers/sqlite.ts @@ -0,0 +1,10 @@ +import Database from "better-sqlite3"; +import { drizzle as drizzleSqlite } from "drizzle-orm/better-sqlite3"; + +import { dbEnv } from "../env"; +import type { SharedDrizzleConfig } from "./shared"; + +export const createSqliteDb = >(config: SharedDrizzleConfig) => { + const connection = new Database(dbEnv.URL); + return drizzleSqlite(connection, config); +}; diff --git a/packages/db/env.ts b/packages/core/src/infrastructure/db/env.ts similarity index 74% rename from packages/db/env.ts rename to packages/core/src/infrastructure/db/env.ts index 9d8932a7d..a4142ade7 100644 --- a/packages/db/env.ts +++ b/packages/core/src/infrastructure/db/env.ts @@ -1,7 +1,6 @@ import { z } from "zod/v4"; -import { env as commonEnv } from "@homarr/common/env"; -import { createEnv } from "@homarr/core/infrastructure/env"; +import { createEnv, runtimeEnvWithPrefix } from "@homarr/core/infrastructure/env"; const drivers = { betterSqlite3: "better-sqlite3", @@ -15,40 +14,40 @@ const onlyAllowUrl = isDriver(drivers.betterSqlite3); const urlRequired = onlyAllowUrl || !isUsingDbHost; const hostRequired = isUsingDbHost && !onlyAllowUrl; -export const env = createEnv({ +export const dbEnv = createEnv({ /** * Specify your server-side environment variables schema here. This way you can ensure the app isn't * built with invalid env vars. */ server: { - DB_DRIVER: z + DRIVER: z .union([z.literal(drivers.betterSqlite3), z.literal(drivers.mysql2), z.literal(drivers.nodePostgres)], { message: `Invalid database driver, supported are ${Object.keys(drivers).join(", ")}`, }) .default(drivers.betterSqlite3), ...(urlRequired ? { - DB_URL: + URL: // Fallback to the default sqlite file path in production - commonEnv.NODE_ENV === "production" && isDriver("better-sqlite3") + process.env.NODE_ENV === "production" && isDriver("better-sqlite3") ? z.string().default("/appdata/db/db.sqlite") : z.string().nonempty(), } : {}), ...(hostRequired ? { - DB_HOST: z.string(), - DB_PORT: z + HOST: z.string(), + PORT: z .string() .regex(/\d+/) .transform(Number) .refine((number) => number >= 1) .default(isDriver(drivers.mysql2) ? 3306 : 5432), - DB_USER: z.string(), - DB_PASSWORD: z.string(), - DB_NAME: z.string(), + USER: z.string(), + PASSWORD: z.string(), + NAME: z.string(), } : {}), }, - experimental__runtimeEnv: process.env, + runtimeEnv: runtimeEnvWithPrefix("DB_"), }); diff --git a/packages/core/src/infrastructure/db/index.ts b/packages/core/src/infrastructure/db/index.ts new file mode 100644 index 000000000..0cad6180f --- /dev/null +++ b/packages/core/src/infrastructure/db/index.ts @@ -0,0 +1,9 @@ +import { createDbMapping } from "./mapping"; + +export { createDb } from "./drivers"; +export const createSchema = createDbMapping; + +export { createMysqlDb } from "./drivers/mysql"; +export { createSqliteDb } from "./drivers/sqlite"; +export { createPostgresDb } from "./drivers/postgresql"; +export { createSharedConfig as createSharedDbConfig } from "./drivers"; diff --git a/packages/core/src/infrastructure/db/mapping.ts b/packages/core/src/infrastructure/db/mapping.ts new file mode 100644 index 000000000..9c82a30b4 --- /dev/null +++ b/packages/core/src/infrastructure/db/mapping.ts @@ -0,0 +1,9 @@ +import { dbEnv } from "./env"; + +type DbMappingInput = Record unknown>; + +export const createDbMapping = (input: TInput) => { + // The DRIVER can be undefined when validation of env vars is skipped + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + return input[dbEnv.DRIVER ?? "better-sqlite3"]() as ReturnType; +}; diff --git a/packages/core/src/infrastructure/dns/env.ts b/packages/core/src/infrastructure/dns/env.ts new file mode 100644 index 000000000..96dd526b8 --- /dev/null +++ b/packages/core/src/infrastructure/dns/env.ts @@ -0,0 +1,8 @@ +import { createBooleanSchema, createEnv } from "../env"; + +export const dnsEnv = createEnv({ + server: { + ENABLE_DNS_CACHING: createBooleanSchema(false), + }, + experimental__runtimeEnv: process.env, +}); diff --git a/packages/common/src/dns.ts b/packages/core/src/infrastructure/dns/init.ts similarity index 74% rename from packages/common/src/dns.ts rename to packages/core/src/infrastructure/dns/init.ts index 7a8578b2b..fa9301576 100644 --- a/packages/common/src/dns.ts +++ b/packages/core/src/infrastructure/dns/init.ts @@ -1,8 +1,8 @@ import { DnsCacheManager } from "dns-caching"; -import { logger } from "@homarr/log"; +import { createLogger } from "@homarr/core/infrastructure/logs"; -import { env } from "../env"; +import { dnsEnv } from "./env"; // Add global type augmentation for homarr declare global { @@ -12,6 +12,8 @@ declare global { }; } +const logger = createLogger({ module: "dns" }); + // Initialize global.homarr if not present // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition global.homarr ??= {}; @@ -21,6 +23,6 @@ global.homarr.dnsCacheManager ??= new DnsCacheManager({ logger, }); -if (env.ENABLE_DNS_CACHING) { +if (dnsEnv.ENABLE_DNS_CACHING) { global.homarr.dnsCacheManager.initialize(); } diff --git a/packages/core/src/infrastructure/http/http-agent.ts b/packages/core/src/infrastructure/http/http-agent.ts new file mode 100644 index 000000000..5b2368215 --- /dev/null +++ b/packages/core/src/infrastructure/http/http-agent.ts @@ -0,0 +1,72 @@ +import type { Dispatcher } from "undici"; +import { Agent } from "undici"; + +import type { ILogger } from "@homarr/core/infrastructure/logs"; +import { createLogger } from "@homarr/core/infrastructure/logs"; + +// The below import statement initializes dns-caching +import "@homarr/core/infrastructure/dns/init"; + +interface HttpAgentOptions extends Agent.Options { + logger?: ILogger; +} + +export class UndiciHttpAgent extends Agent { + private logger: ILogger; + + constructor(props?: HttpAgentOptions) { + super(props); + + this.logger = props?.logger ?? createLogger({ module: "httpAgent" }); + } + + dispatch(options: Dispatcher.DispatchOptions, handler: Dispatcher.DispatchHandler): boolean { + this.logRequestDispatch(options); + return super.dispatch(options, handler); + } + + private logRequestDispatch(options: Dispatcher.DispatchOptions) { + const path = this.redactPathParams(options.path); + let url = new URL(`${options.origin as string}${path}`); + url = this.redactSearchParams(url); + + this.logger.debug( + `Dispatching request ${url.toString().replaceAll("=&", "&")} (${Object.keys(options.headers ?? {}).length} headers)`, + ); + } + + /** + * Redact path parameters that are longer than 32 characters + * This is to prevent sensitive data from being logged + * @param path path of the request + * @returns redacted path + */ + private redactPathParams(path: string): string { + return path + .split("/") + .map((segment) => (segment.length >= 32 && !segment.startsWith("?") ? "REDACTED" : segment)) + .join("/"); + } + + /** + * Redact sensitive search parameters from the URL. + * It allows certain patterns to remain unredacted. + * Like small numbers, booleans, short strings, dates, and date-times. + * Some integrations use query parameters for auth. + * @param url URL object of the request + * @returns redacted URL object + */ + private redactSearchParams(url: URL): URL { + url.searchParams.forEach((value, key) => { + if (value === "") return; // Skip empty values + if (/^-?\d{1,12}$/.test(value)) return; // Skip small numbers + if (value === "true" || value === "false") return; // Skip boolean values + if (/^[a-zA-Z]{1,12}$/.test(value)) return; // Skip short strings + if (/^\d{4}-\d{2}-\d{2}$/.test(value)) return; // Skip dates + if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value)) return; // Skip date times + + url.searchParams.set(key, "REDACTED"); + }); + return url; + } +} diff --git a/packages/core/src/infrastructure/http/index.ts b/packages/core/src/infrastructure/http/index.ts new file mode 100644 index 000000000..572388f2a --- /dev/null +++ b/packages/core/src/infrastructure/http/index.ts @@ -0,0 +1,8 @@ +export { UndiciHttpAgent } from "./http-agent"; +export { + createAxiosCertificateInstanceAsync, + createCertificateAgentAsync, + createCustomCheckServerIdentity, + createHttpsAgentAsync, + fetchWithTrustedCertificatesAsync, +} from "./request"; diff --git a/packages/core/src/infrastructure/http/request.ts b/packages/core/src/infrastructure/http/request.ts new file mode 100644 index 000000000..b8dcf962e --- /dev/null +++ b/packages/core/src/infrastructure/http/request.ts @@ -0,0 +1,82 @@ +import type { AgentOptions } from "node:https"; +import { Agent as HttpsAgent } from "node:https"; +import { checkServerIdentity } from "node:tls"; +import axios from "axios"; +import type { RequestInfo, RequestInit, Response } from "undici"; +import { fetch } from "undici"; + +import { + getAllTrustedCertificatesAsync, + getTrustedCertificateHostnamesAsync, +} from "@homarr/core/infrastructure/certificates"; +import { UndiciHttpAgent } from "@homarr/core/infrastructure/http"; + +import type { TrustedCertificateHostname } from "../certificates/hostnames"; +import { withTimeoutAsync } from "./timeout"; + +export const createCustomCheckServerIdentity = ( + trustedHostnames: TrustedCertificateHostname[], +): typeof checkServerIdentity => { + return (hostname, peerCertificate) => { + const matchingTrustedHostnames = trustedHostnames.filter( + (cert) => cert.thumbprint === peerCertificate.fingerprint256, + ); + + // We trust the certificate if we have a matching hostname + if (matchingTrustedHostnames.some((cert) => cert.hostname === hostname)) return undefined; + + return checkServerIdentity(hostname, peerCertificate); + }; +}; + +export const createCertificateAgentAsync = async (override?: { + ca: string | string[]; + checkServerIdentity: typeof checkServerIdentity; +}) => { + return new UndiciHttpAgent({ + connect: override ?? { + ca: await getAllTrustedCertificatesAsync(), + checkServerIdentity: createCustomCheckServerIdentity(await getTrustedCertificateHostnamesAsync()), + }, + }); +}; + +export const createHttpsAgentAsync = async (override?: Pick) => { + return new HttpsAgent( + override ?? { + ca: await getAllTrustedCertificatesAsync(), + checkServerIdentity: createCustomCheckServerIdentity(await getTrustedCertificateHostnamesAsync()), + }, + ); +}; + +export const createAxiosCertificateInstanceAsync = async ( + override?: Pick, +) => { + return axios.create({ + httpsAgent: await createHttpsAgentAsync(override), + }); +}; + +export const fetchWithTrustedCertificatesAsync = async ( + url: RequestInfo, + options?: RequestInit & { timeout?: number }, +): Promise => { + const agent = await createCertificateAgentAsync(undefined); + if (options?.timeout) { + return await withTimeoutAsync( + async (signal) => + fetch(url, { + ...options, + signal, + dispatcher: agent, + }), + options.timeout, + ); + } + + return fetch(url, { + ...options, + dispatcher: agent, + }); +}; diff --git a/packages/core/src/infrastructure/http/timeout.ts b/packages/core/src/infrastructure/http/timeout.ts new file mode 100644 index 000000000..e56bd5e56 --- /dev/null +++ b/packages/core/src/infrastructure/http/timeout.ts @@ -0,0 +1,19 @@ +import type { Response as UndiciResponse } from "undici"; + +// https://stackoverflow.com/questions/46946380/fetch-api-request-timeout +export const withTimeoutAsync = async ( + callback: (signal: AbortSignal) => Promise, + timeout = 10000, +) => { + const controller = new AbortController(); + + const timeoutId = setTimeout(() => controller.abort(), timeout); + + return await callback(controller.signal).finally(() => { + clearTimeout(timeoutId); + }); +}; + +export const fetchWithTimeoutAsync = async (...[url, requestInit]: Parameters) => { + return await withTimeoutAsync((signal) => fetch(url, { ...requestInit, signal })); +}; diff --git a/packages/log/src/constants.ts b/packages/core/src/infrastructure/logs/constants.ts similarity index 100% rename from packages/log/src/constants.ts rename to packages/core/src/infrastructure/logs/constants.ts diff --git a/packages/core/src/infrastructure/logs/env.ts b/packages/core/src/infrastructure/logs/env.ts new file mode 100644 index 000000000..5e8a744b6 --- /dev/null +++ b/packages/core/src/infrastructure/logs/env.ts @@ -0,0 +1,11 @@ +import { z } from "zod/v4"; + +import { createEnv, runtimeEnvWithPrefix } from "../env"; +import { logLevels } from "./constants"; + +export const logsEnv = createEnv({ + server: { + LEVEL: z.enum(logLevels).default("info"), + }, + runtimeEnv: runtimeEnvWithPrefix("LOG_"), +}); diff --git a/packages/core/src/infrastructure/logs/error.ts b/packages/core/src/infrastructure/logs/error.ts new file mode 100644 index 000000000..6224bd922 --- /dev/null +++ b/packages/core/src/infrastructure/logs/error.ts @@ -0,0 +1,9 @@ +export class ErrorWithMetadata extends Error { + public metadata: Record; + + constructor(message: string, metadata: Record = {}, options?: ErrorOptions) { + super(message, options); + this.name = "Error"; + this.metadata = metadata; + } +} diff --git a/packages/log/src/error.ts b/packages/core/src/infrastructure/logs/format/error.ts similarity index 52% rename from packages/log/src/error.ts rename to packages/core/src/infrastructure/logs/format/error.ts index de12da32f..2e5181a4f 100644 --- a/packages/log/src/error.ts +++ b/packages/core/src/infrastructure/logs/format/error.ts @@ -1,5 +1,10 @@ +import { logsEnv } from "../env"; import { formatMetadata } from "./metadata"; +const ERROR_OBJECT_PRUNE_DEPTH = logsEnv.LEVEL === "debug" ? 10 : 3; +const ERROR_STACK_LINE_LIMIT = logsEnv.LEVEL === "debug" ? undefined : 5; +const ERROR_CAUSE_DEPTH = logsEnv.LEVEL === "debug" ? 10 : 5; + /** * Formats the cause of an error in the format * @example caused by Error: {message} @@ -10,7 +15,7 @@ import { formatMetadata } from "./metadata"; */ export const formatErrorCause = (cause: unknown, iteration = 0): string => { // Prevent infinite recursion - if (iteration > 5) { + if (iteration > ERROR_CAUSE_DEPTH) { return ""; } @@ -22,8 +27,12 @@ export const formatErrorCause = (cause: unknown, iteration = 0): string => { return `\ncaused by ${formatErrorTitle(cause)}\n${formatErrorStack(cause.stack)}${formatErrorCause(cause.cause, iteration + 1)}`; } - if (cause instanceof Object) { - return `\ncaused by ${JSON.stringify(cause)}`; + if (typeof cause === "object" && cause !== null) { + if ("cause" in cause) { + const { cause: innerCause, ...rest } = cause; + return `\ncaused by ${JSON.stringify(prune(rest, ERROR_OBJECT_PRUNE_DEPTH))}${formatErrorCause(innerCause, iteration + 1)}`; + } + return `\ncaused by ${JSON.stringify(prune(cause, ERROR_OBJECT_PRUNE_DEPTH))}`; } return `\ncaused by ${cause as string}`; @@ -50,5 +59,28 @@ export const formatErrorTitle = (error: Error) => { * @param stack stack trace * @returns formatted stack trace */ -export const formatErrorStack = (stack: string | undefined) => (stack ? removeFirstLine(stack) : ""); -const removeFirstLine = (stack: string) => stack.split("\n").slice(1).join("\n"); +export const formatErrorStack = (stack: string | undefined) => + stack + ?.split("\n") + .slice(1, ERROR_STACK_LINE_LIMIT ? ERROR_STACK_LINE_LIMIT + 1 : undefined) + .join("\n") ?? ""; + +/** + * Removes nested properties from an object beyond a certain depth + */ +const prune = (value: unknown, depth: number): unknown => { + if (typeof value !== "object" || value === null) { + return value; + } + + if (Array.isArray(value)) { + if (depth === 0) return []; + return value.map((item) => prune(item, depth - 1)); + } + + if (depth === 0) { + return {}; + } + + return Object.fromEntries(Object.entries(value).map(([key, val]) => [key, prune(val, depth - 1)])); +}; diff --git a/packages/core/src/infrastructure/logs/format/index.ts b/packages/core/src/infrastructure/logs/format/index.ts new file mode 100644 index 000000000..1d86ceb01 --- /dev/null +++ b/packages/core/src/infrastructure/logs/format/index.ts @@ -0,0 +1,25 @@ +import { format } from "winston"; + +import { formatErrorCause, formatErrorStack } from "./error"; +import { formatMetadata } from "./metadata"; + +export const logFormat = format.combine( + format.colorize(), + format.timestamp(), + format.errors({ stack: true, cause: true }), + format.printf(({ level, message, timestamp, cause, stack, ...metadata }) => { + const firstLine = `${timestamp as string} ${level}: ${message as string} ${formatMetadata(metadata)}`; + + if (!cause && !stack) { + return firstLine; + } + + const formatedStack = formatErrorStack(stack as string | undefined); + + if (!cause) { + return `${firstLine}\n${formatedStack}`; + } + + return `${firstLine}\n${formatedStack}${formatErrorCause(cause)}`; + }), +); diff --git a/packages/log/src/metadata.ts b/packages/core/src/infrastructure/logs/format/metadata.ts similarity index 51% rename from packages/log/src/metadata.ts rename to packages/core/src/infrastructure/logs/format/metadata.ts index 089a14caa..057aa157d 100644 --- a/packages/log/src/metadata.ts +++ b/packages/core/src/infrastructure/logs/format/metadata.ts @@ -1,7 +1,11 @@ +import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; + export const formatMetadata = (metadata: Record | Error, ignoreKeys?: string[]) => { - const filteredMetadata = Object.keys(metadata) + const metadataObject = metadata instanceof ErrorWithMetadata ? metadata.metadata : metadata; + + const filteredMetadata = Object.keys(metadataObject) .filter((key) => !ignoreKeys?.includes(key)) - .map((key) => ({ key, value: metadata[key as keyof typeof metadata] })) + .map((key) => ({ key, value: metadataObject[key as keyof typeof metadataObject] })) .filter(({ value }) => typeof value !== "object" && typeof value !== "function"); return filteredMetadata.map(({ key, value }) => `${key}="${value as string}"`).join(" "); diff --git a/packages/core/src/infrastructure/logs/index.ts b/packages/core/src/infrastructure/logs/index.ts new file mode 100644 index 000000000..df70b5578 --- /dev/null +++ b/packages/core/src/infrastructure/logs/index.ts @@ -0,0 +1,26 @@ +import winston from "winston"; + +import { logsEnv } from "./env"; +import { logFormat } from "./format"; +import { logTransports } from "./transports"; + +const logger = winston.createLogger({ + format: logFormat, + transports: logTransports, + level: logsEnv.LEVEL, +}); + +interface DefaultMetadata { + module: string; +} + +export const createLogger = (metadata: DefaultMetadata & Record) => logger.child(metadata); + +type LogMethod = ((message: string, metadata?: Record) => void) | ((error: unknown) => void); + +export interface ILogger { + debug: LogMethod; + info: LogMethod; + warn: LogMethod; + error: LogMethod; +} diff --git a/packages/core/src/infrastructure/logs/transports/index.ts b/packages/core/src/infrastructure/logs/transports/index.ts new file mode 100644 index 000000000..d9789e917 --- /dev/null +++ b/packages/core/src/infrastructure/logs/transports/index.ts @@ -0,0 +1,21 @@ +import { transports } from "winston"; +import type { transport } from "winston"; + +import { RedisTransport } from "./redis-transport"; + +const getTransports = () => { + const defaultTransports: transport[] = [new transports.Console()]; + + // Only add the Redis transport if we are not in CI + if (!(Boolean(process.env.CI) || Boolean(process.env.DISABLE_REDIS_LOGS))) { + return defaultTransports.concat( + new RedisTransport({ + level: "debug", + }), + ); + } + + return defaultTransports; +}; + +export const logTransports = getTransports(); diff --git a/packages/log/src/redis-transport.ts b/packages/core/src/infrastructure/logs/transports/redis-transport.ts similarity index 82% rename from packages/log/src/redis-transport.ts rename to packages/core/src/infrastructure/logs/transports/redis-transport.ts index a674803bd..e063f60dd 100644 --- a/packages/log/src/redis-transport.ts +++ b/packages/core/src/infrastructure/logs/transports/redis-transport.ts @@ -1,8 +1,8 @@ import superjson from "superjson"; import Transport from "winston-transport"; -import type { RedisClient } from "@homarr/core/infrastructure/redis"; -import { createRedisClient } from "@homarr/core/infrastructure/redis"; +import type { RedisClient } from "../../redis/client"; +import { createRedisClient } from "../../redis/client"; const messageSymbol = Symbol.for("message"); const levelSymbol = Symbol.for("level"); @@ -13,6 +13,7 @@ const levelSymbol = Symbol.for("level"); // export class RedisTransport extends Transport { private redis: RedisClient | null = null; + public static readonly publishChannel = "pubSub:logging"; /** * Log the info to the Redis channel @@ -27,7 +28,7 @@ export class RedisTransport extends Transport { this.redis .publish( - "pubSub:logging", + RedisTransport.publishChannel, superjson.stringify({ message: info[messageSymbol], level: info[levelSymbol], diff --git a/packages/common/src/test/fetch-agent.spec.ts b/packages/core/src/test/infrastructure/http/http-agent.spec.ts similarity index 72% rename from packages/common/src/test/fetch-agent.spec.ts rename to packages/core/src/test/infrastructure/http/http-agent.spec.ts index 9617a9587..52d6b09e8 100644 --- a/packages/common/src/test/fetch-agent.spec.ts +++ b/packages/core/src/test/infrastructure/http/http-agent.spec.ts @@ -1,9 +1,9 @@ import type { Dispatcher } from "undici"; import { describe, expect, test, vi } from "vitest"; -import { logger } from "@homarr/log"; +import { UndiciHttpAgent } from "@homarr/core/infrastructure/http"; -import { LoggingAgent } from "../fetch-agent"; +import { TestLogger } from "../logs"; vi.mock("undici", () => { return { @@ -18,23 +18,26 @@ vi.mock("undici", () => { const REDACTED = "REDACTED"; -describe("LoggingAgent should log all requests", () => { +describe("UndiciHttpAgent should log all requests", () => { test("should log all requests", () => { // Arrange - const infoLogSpy = vi.spyOn(logger, "debug"); - const agent = new LoggingAgent(); + const logger = new TestLogger(); + const agent = new UndiciHttpAgent({ logger }); // Act agent.dispatch({ origin: "https://homarr.dev", path: "/", method: "GET" }, {}); // Assert - expect(infoLogSpy).toHaveBeenCalledWith("Dispatching request https://homarr.dev/ (0 headers)"); + expect(logger.messages).toContainEqual({ + level: "debug", + message: "Dispatching request https://homarr.dev/ (0 headers)", + }); }); test("should show amount of headers", () => { // Arrange - const infoLogSpy = vi.spyOn(logger, "debug"); - const agent = new LoggingAgent(); + const logger = new TestLogger(); + const agent = new UndiciHttpAgent({ logger }); // Act agent.dispatch( @@ -51,7 +54,7 @@ describe("LoggingAgent should log all requests", () => { ); // Assert - expect(infoLogSpy).toHaveBeenCalledWith(expect.stringContaining("(2 headers)")); + expect(logger.messages.at(-1)?.message).toContain("(2 headers)"); }); test.each([ @@ -69,14 +72,14 @@ describe("LoggingAgent should log all requests", () => { [`/${"a".repeat(32)}/?param=123`, `/${REDACTED}/?param=123`], ])("should redact sensitive data in url https://homarr.dev%s", (path, expected) => { // Arrange - const infoLogSpy = vi.spyOn(logger, "debug"); - const agent = new LoggingAgent(); + const logger = new TestLogger(); + const agent = new UndiciHttpAgent({ logger }); // Act agent.dispatch({ origin: "https://homarr.dev", path, method: "GET" }, {}); // Assert - expect(infoLogSpy).toHaveBeenCalledWith(expect.stringContaining(` https://homarr.dev${expected} `)); + expect(logger.messages.at(-1)?.message).toContain(` https://homarr.dev${expected} `); }); test.each([ ["empty", "/?empty"], @@ -88,13 +91,13 @@ describe("LoggingAgent should log all requests", () => { ["date times", "/?datetime=2022-01-01T00:00:00.000Z"], ])("should not redact values that are %s", (_reason, path) => { // Arrange - const infoLogSpy = vi.spyOn(logger, "debug"); - const agent = new LoggingAgent(); + const logger = new TestLogger(); + const agent = new UndiciHttpAgent({ logger }); // Act agent.dispatch({ origin: "https://homarr.dev", path, method: "GET" }, {}); // Assert - expect(infoLogSpy).toHaveBeenCalledWith(expect.stringContaining(` https://homarr.dev${path} `)); + expect(logger.messages.at(-1)?.message).toContain(` https://homarr.dev${path} `); }); }); diff --git a/packages/core/src/test/infrastructure/logs/index.ts b/packages/core/src/test/infrastructure/logs/index.ts new file mode 100644 index 000000000..1e2c6c282 --- /dev/null +++ b/packages/core/src/test/infrastructure/logs/index.ts @@ -0,0 +1,49 @@ +import type { ILogger } from "@homarr/core/infrastructure/logs"; +import type { LogLevel } from "@homarr/core/infrastructure/logs/constants"; + +interface LogMessage { + level: LogLevel; + message: string; + meta?: Record; +} + +interface LogError { + level: LogLevel; + error: unknown; +} + +type LogEntry = LogMessage | LogError; + +export class TestLogger implements ILogger { + public entries: LogEntry[] = []; + public get messages(): LogMessage[] { + return this.entries.filter((entry) => "message" in entry); + } + public get errors(): LogError[] { + return this.entries.filter((entry) => "error" in entry); + } + + private log(level: LogLevel, param1: unknown, param2?: Record): void { + if (typeof param1 === "string") { + this.entries.push({ level, message: param1, meta: param2 }); + } else { + this.entries.push({ level, error: param1 }); + } + } + + debug(param1: unknown, param2?: Record): void { + this.log("debug", param1, param2); + } + + info(param1: unknown, param2?: Record): void { + this.log("info", param1, param2); + } + + warn(param1: unknown, param2?: Record): void { + this.log("warn", param1, param2); + } + + error(param1: unknown, param2?: Record): void { + this.log("error", param1, param2); + } +} diff --git a/packages/cron-job-api/package.json b/packages/cron-job-api/package.json index a784b74f0..a268193f2 100644 --- a/packages/cron-job-api/package.json +++ b/packages/cron-job-api/package.json @@ -28,13 +28,12 @@ "@homarr/common": "workspace:^0.1.0", "@homarr/core": "workspace:^0.1.0", "@homarr/cron-jobs": "workspace:^0.1.0", - "@homarr/log": "workspace:^0.1.0", "@tanstack/react-query": "^5.90.12", - "@trpc/client": "^11.7.2", - "@trpc/server": "^11.7.2", - "@trpc/tanstack-react-query": "^11.7.2", + "@trpc/client": "^11.8.0", + "@trpc/server": "^11.8.0", + "@trpc/tanstack-react-query": "^11.8.0", "node-cron": "^4.2.1", - "react": "19.2.1", + "react": "19.2.3", "zod": "^4.1.13" }, "devDependencies": { @@ -43,7 +42,7 @@ "@homarr/tsconfig": "workspace:^0.1.0", "@types/node-cron": "^3.0.11", "@types/react": "19.2.7", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/cron-job-status/package.json b/packages/cron-job-status/package.json index 675b171f2..ffde92257 100644 --- a/packages/cron-job-status/package.json +++ b/packages/cron-job-status/package.json @@ -29,7 +29,7 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/cron-jobs-core/package.json b/packages/cron-jobs-core/package.json index 01b4f86f6..ea6e12305 100644 --- a/packages/cron-jobs-core/package.json +++ b/packages/cron-jobs-core/package.json @@ -25,6 +25,7 @@ "prettier": "@homarr/prettier-config", "dependencies": { "@homarr/common": "workspace:^0.1.0", + "@homarr/core": "workspace:^", "@homarr/db": "workspace:^0.1.0", "node-cron": "^4.2.1" }, @@ -33,7 +34,7 @@ "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", "@types/node-cron": "^3.0.11", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/cron-jobs-core/src/creator.ts b/packages/cron-jobs-core/src/creator.ts index e21065d9e..c22598295 100644 --- a/packages/cron-jobs-core/src/creator.ts +++ b/packages/cron-jobs-core/src/creator.ts @@ -1,8 +1,8 @@ -import { AxiosError } from "axios"; import { createTask, validate } from "node-cron"; import { Stopwatch } from "@homarr/common"; import type { MaybePromise } from "@homarr/common/types"; +import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; import { db } from "@homarr/db"; import type { Logger } from "./logger"; @@ -33,33 +33,39 @@ const createCallback = MaybePromise) => { const catchingCallbackAsync = async () => { try { - creatorOptions.logger.logDebug(`The callback of '${name}' cron job started`); + creatorOptions.logger.logDebug("The callback of cron job started", { + name, + }); const stopwatch = new Stopwatch(); await creatorOptions.beforeCallback?.(name); const beforeCallbackTook = stopwatch.getElapsedInHumanWords(); await callback(); const callbackTook = stopwatch.getElapsedInHumanWords(); - creatorOptions.logger.logDebug( - `The callback of '${name}' cron job succeeded (before callback took ${beforeCallbackTook}, callback took ${callbackTook})`, - ); + creatorOptions.logger.logDebug("The callback of cron job succeeded", { + name, + beforeCallbackTook, + callbackTook, + }); const durationInMillis = stopwatch.getElapsedInMilliseconds(); if (durationInMillis > expectedMaximumDurationInMillis) { - creatorOptions.logger.logWarning( - `The callback of '${name}' succeeded but took ${(durationInMillis - expectedMaximumDurationInMillis).toFixed(2)}ms longer than expected (${expectedMaximumDurationInMillis}ms). This may indicate that your network performance, host performance or something else is too slow. If this happens too often, it should be looked into.`, - ); + creatorOptions.logger.logWarning("The callback of cron job took longer than expected", { + name, + durationInMillis, + expectedMaximumDurationInMillis, + }); } await creatorOptions.onCallbackSuccess?.(name); } catch (error) { - // Log AxiosError in a less detailed way to prevent very long output - if (error instanceof AxiosError) { - creatorOptions.logger.logError( - `Failed to run job '${name}': [AxiosError] ${error.message} ${error.response?.status} ${error.response?.config.url}\n${error.stack}`, - ); - } else { - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - creatorOptions.logger.logError(`Failed to run job '${name}': ${error}`); - } + creatorOptions.logger.logError( + new ErrorWithMetadata( + "The callback of cron job failed", + { + name, + }, + { cause: error }, + ), + ); await creatorOptions.onCallbackError?.(name, error); } }; @@ -80,21 +86,28 @@ const createCallback = ( defaultCronExpression: TExpression, options: CreateCronJobOptions = { runOnStart: false }, ) => { - creatorOptions.logger.logDebug(`Validating cron expression '${defaultCronExpression}' for job: ${name}`); + creatorOptions.logger.logDebug("Validating cron expression for cron job", { + name, + cronExpression: defaultCronExpression, + }); if (!validate(defaultCronExpression)) { throw new Error(`Invalid cron expression '${defaultCronExpression}' for job '${name}'`); } - creatorOptions.logger.logDebug(`Cron job expression '${defaultCronExpression}' for job ${name} is valid`); + creatorOptions.logger.logDebug("Cron job expression for cron job is valid", { + name, + cronExpression: defaultCronExpression, + }); const returnValue = { withCallback: createCallback(name, defaultCronExpression, options, creatorOptions), diff --git a/packages/cron-jobs-core/src/group.ts b/packages/cron-jobs-core/src/group.ts index 28ae714c8..77cd6057d 100644 --- a/packages/cron-jobs-core/src/group.ts +++ b/packages/cron-jobs-core/src/group.ts @@ -19,11 +19,15 @@ export const createJobGroupCreator = ( options: CreateCronJobGroupCreatorOptions, ) => { return >(jobs: TJobs) => { - options.logger.logDebug(`Creating job group with ${Object.keys(jobs).length} jobs.`); + options.logger.logDebug("Creating job group.", { + jobCount: Object.keys(jobs).length, + }); for (const [key, job] of objectEntries(jobs)) { if (typeof key !== "string" || typeof job.name !== "string") continue; - options.logger.logDebug(`Added job ${job.name} to the job registry.`); + options.logger.logDebug("Registering job in the job registry.", { + name: job.name, + }); jobRegistry.set(key, { ...job, name: job.name, @@ -54,7 +58,9 @@ export const createJobGroupCreator = ( if (!job) return; if (!tasks.has(job.name)) return; - options.logger.logInfo(`Starting schedule cron job ${job.name}.`); + options.logger.logInfo("Starting schedule of cron job.", { + name: job.name, + }); await job.onStartAsync(); await tasks.get(name as string)?.start(); }, @@ -64,7 +70,9 @@ export const createJobGroupCreator = ( continue; } - options.logger.logInfo(`Starting schedule of cron job ${job.name}.`); + options.logger.logInfo("Starting schedule of cron job.", { + name: job.name, + }); await job.onStartAsync(); await tasks.get(job.name)?.start(); } @@ -76,19 +84,25 @@ export const createJobGroupCreator = ( throw new Error(`The job "${job.name}" can not be executed manually.`); } - options.logger.logInfo(`Running schedule cron job ${job.name} manually.`); + options.logger.logInfo("Running schedule cron job manually.", { + name: job.name, + }); await tasks.get(name as string)?.execute(); }, stopAsync: async (name: keyof TJobs) => { const job = jobRegistry.get(name as string); if (!job) return; - options.logger.logInfo(`Stopping schedule cron job ${job.name}.`); + options.logger.logInfo("Stopping schedule of cron job.", { + name: job.name, + }); await tasks.get(name as string)?.stop(); }, stopAllAsync: async () => { for (const job of jobRegistry.values()) { - options.logger.logInfo(`Stopping schedule cron job ${job.name}.`); + options.logger.logInfo("Stopping schedule of cron job.", { + name: job.name, + }); await tasks.get(job.name)?.stop(); } }, diff --git a/packages/cron-jobs-core/src/index.ts b/packages/cron-jobs-core/src/index.ts index 08ed3e75e..ee9563f08 100644 --- a/packages/cron-jobs-core/src/index.ts +++ b/packages/cron-jobs-core/src/index.ts @@ -1,10 +1,9 @@ import type { CreateCronJobCreatorOptions } from "./creator"; import { createCronJobCreator } from "./creator"; import { createJobGroupCreator } from "./group"; -import { ConsoleLogger } from "./logger"; export const createCronJobFunctions = ( - options: CreateCronJobCreatorOptions = { logger: new ConsoleLogger() }, + options: CreateCronJobCreatorOptions, ) => { return { createCronJob: createCronJobCreator(options), diff --git a/packages/cron-jobs-core/src/logger.ts b/packages/cron-jobs-core/src/logger.ts index 73628b31c..ff0c1b699 100644 --- a/packages/cron-jobs-core/src/logger.ts +++ b/packages/cron-jobs-core/src/logger.ts @@ -1,24 +1,7 @@ export interface Logger { - logDebug(message: string): void; - logInfo(message: string): void; + logDebug(message: string, metadata?: Record): void; + logInfo(message: string, metadata?: Record): void; + logError(message: string, metadata?: Record): void; logError(error: unknown): void; - logWarning(message: string): void; -} - -export class ConsoleLogger implements Logger { - public logDebug(message: string) { - console.log(message); - } - - public logInfo(message: string) { - console.log(message); - } - - public logError(error: unknown) { - console.error(error); - } - - public logWarning(message: string) { - console.warn(message); - } + logWarning(message: string, metadata?: Record): void; } diff --git a/packages/cron-jobs/package.json b/packages/cron-jobs/package.json index d3baaa48b..1bf4e090e 100644 --- a/packages/cron-jobs/package.json +++ b/packages/cron-jobs/package.json @@ -25,13 +25,13 @@ "@homarr/analytics": "workspace:^0.1.0", "@homarr/auth": "workspace:^0.1.0", "@homarr/common": "workspace:^0.1.0", + "@homarr/core": "workspace:^0.1.0", "@homarr/cron-job-status": "workspace:^0.1.0", "@homarr/cron-jobs-core": "workspace:^0.1.0", "@homarr/db": "workspace:^0.1.0", "@homarr/definitions": "workspace:^0.1.0", "@homarr/icons": "workspace:^0.1.0", "@homarr/integrations": "workspace:^0.1.0", - "@homarr/log": "workspace:^0.1.0", "@homarr/ping": "workspace:^0.1.0", "@homarr/redis": "workspace:^0.1.0", "@homarr/request-handler": "workspace:^0.1.0", @@ -44,7 +44,7 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/cron-jobs/src/jobs/docker.ts b/packages/cron-jobs/src/jobs/docker.ts index 4f19d341c..963f395bf 100644 --- a/packages/cron-jobs/src/jobs/docker.ts +++ b/packages/cron-jobs/src/jobs/docker.ts @@ -1,14 +1,17 @@ import SuperJSON from "superjson"; +import { createLogger } from "@homarr/core/infrastructure/logs"; +import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; import { EVERY_MINUTE } from "@homarr/cron-jobs-core/expressions"; import { db, eq } from "@homarr/db"; import { items } from "@homarr/db/schema"; -import { logger } from "@homarr/log"; import { dockerContainersRequestHandler } from "@homarr/request-handler/docker"; import type { WidgetComponentProps } from "../../../widgets"; import { createCronJob } from "../lib"; +const logger = createLogger({ module: "dockerJobs" }); + export const dockerContainersJob = createCronJob("dockerContainers", EVERY_MINUTE).withCallback(async () => { const dockerItems = await db.query.items.findMany({ where: eq(items.kind, "dockerContainers"), @@ -21,7 +24,7 @@ export const dockerContainersJob = createCronJob("dockerContainers", EVERY_MINUT const innerHandler = dockerContainersRequestHandler.handler(options); await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: true }); } catch (error) { - logger.error("Failed to update Docker container status", { item, error }); + logger.error(new ErrorWithMetadata("Failed to update Docker container status", { item }, { cause: error })); } }), ); diff --git a/packages/cron-jobs/src/jobs/icons-updater.ts b/packages/cron-jobs/src/jobs/icons-updater.ts index 64332de51..b48c26ea6 100644 --- a/packages/cron-jobs/src/jobs/icons-updater.ts +++ b/packages/cron-jobs/src/jobs/icons-updater.ts @@ -1,14 +1,16 @@ import { createId, splitToNChunks, Stopwatch } from "@homarr/common"; import { env } from "@homarr/common/env"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { EVERY_WEEK } from "@homarr/cron-jobs-core/expressions"; import type { InferInsertModel } from "@homarr/db"; import { db, handleTransactionsAsync, inArray, sql } from "@homarr/db"; import { iconRepositories, icons } from "@homarr/db/schema"; import { fetchIconsAsync } from "@homarr/icons"; -import { logger } from "@homarr/log"; import { createCronJob } from "../lib"; +const logger = createLogger({ module: "iconsUpdaterJobs" }); + export const iconsUpdaterJob = createCronJob("iconsUpdater", EVERY_WEEK, { runOnStart: true, expectedMaximumDurationInMillis: 10 * 1000, @@ -21,9 +23,11 @@ export const iconsUpdaterJob = createCronJob("iconsUpdater", EVERY_WEEK, { const countIcons = repositoryIconGroups .map((group) => group.icons.length) .reduce((partialSum, arrayLength) => partialSum + arrayLength, 0); - logger.info( - `Successfully fetched ${countIcons} icons from ${repositoryIconGroups.length} repositories within ${stopWatch.getElapsedInHumanWords()}`, - ); + logger.info("Fetched icons from repositories", { + repositoryCount: repositoryIconGroups.length, + iconCount: countIcons, + duration: stopWatch.getElapsedInHumanWords(), + }); const databaseIconRepositories = await db.query.iconRepositories.findMany({ with: { @@ -162,5 +166,9 @@ export const iconsUpdaterJob = createCronJob("iconsUpdater", EVERY_WEEK, { }, }); - logger.info(`Updated database within ${stopWatch.getElapsedInHumanWords()} (-${countDeleted}, +${countInserted})`); + logger.info("Updated icons in database", { + duration: stopWatch.getElapsedInHumanWords(), + added: countInserted, + deleted: countDeleted, + }); }); diff --git a/packages/cron-jobs/src/jobs/ping.ts b/packages/cron-jobs/src/jobs/ping.ts index 318db80bd..bfe97bbda 100644 --- a/packages/cron-jobs/src/jobs/ping.ts +++ b/packages/cron-jobs/src/jobs/ping.ts @@ -1,12 +1,15 @@ +import { createLogger } from "@homarr/core/infrastructure/logs"; +import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; import { EVERY_MINUTE } from "@homarr/cron-jobs-core/expressions"; import { db } from "@homarr/db"; import { getServerSettingByKeyAsync } from "@homarr/db/queries"; -import { logger } from "@homarr/log"; import { sendPingRequestAsync } from "@homarr/ping"; import { pingChannel, pingUrlChannel } from "@homarr/redis"; import { createCronJob } from "../lib"; +const logger = createLogger({ module: "pingJobs" }); + const resetPreviousUrlsAsync = async () => { await pingUrlChannel.clearAsync(); logger.info("Cleared previous ping urls"); @@ -31,9 +34,9 @@ const pingAsync = async (url: string) => { const pingResult = await sendPingRequestAsync(url); if ("statusCode" in pingResult) { - logger.debug(`executed ping for url ${url} with status code ${pingResult.statusCode}`); + logger.debug("Executed ping successfully", { url, statusCode: pingResult.statusCode }); } else { - logger.error(`Executing ping for url ${url} failed with error: ${pingResult.error}`); + logger.error(new ErrorWithMetadata("Executing ping failed", { url }, { cause: pingResult.error })); } await pingChannel.publishAsync({ diff --git a/packages/cron-jobs/src/jobs/rss-feeds.ts b/packages/cron-jobs/src/jobs/rss-feeds.ts index 15b883716..2dda83499 100644 --- a/packages/cron-jobs/src/jobs/rss-feeds.ts +++ b/packages/cron-jobs/src/jobs/rss-feeds.ts @@ -1,15 +1,18 @@ import SuperJSON from "superjson"; +import { createLogger } from "@homarr/core/infrastructure/logs"; +import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; import { EVERY_10_MINUTES } from "@homarr/cron-jobs-core/expressions"; import { db, eq } from "@homarr/db"; import { items } from "@homarr/db/schema"; -import { logger } from "@homarr/log"; // This import is done that way to avoid circular dependencies. import { rssFeedsRequestHandler } from "@homarr/request-handler/rss-feeds"; import type { WidgetComponentProps } from "../../../widgets"; import { createCronJob } from "../lib"; +const logger = createLogger({ module: "rssFeedsJobs" }); + export const rssFeedsJob = createCronJob("rssFeeds", EVERY_10_MINUTES).withCallback(async () => { const rssItems = await db.query.items.findMany({ where: eq(items.kind, "rssFeed"), @@ -29,7 +32,7 @@ export const rssFeedsJob = createCronJob("rssFeeds", EVERY_10_MINUTES).withCallb forceUpdate: true, }); } catch (error) { - logger.error("Failed to update RSS feed", { url, error }); + logger.error(new ErrorWithMetadata("Failed to update RSS feed", { url }, { cause: error })); } } } diff --git a/packages/cron-jobs/src/jobs/weather.ts b/packages/cron-jobs/src/jobs/weather.ts index 20f96728a..1b23ade4c 100644 --- a/packages/cron-jobs/src/jobs/weather.ts +++ b/packages/cron-jobs/src/jobs/weather.ts @@ -1,14 +1,17 @@ import SuperJSON from "superjson"; +import { createLogger } from "@homarr/core/infrastructure/logs"; +import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; import { EVERY_10_MINUTES } from "@homarr/cron-jobs-core/expressions"; import { db, eq } from "@homarr/db"; import { items } from "@homarr/db/schema"; -import { logger } from "@homarr/log"; import { weatherRequestHandler } from "@homarr/request-handler/weather"; import type { WidgetComponentProps } from "../../../widgets"; import { createCronJob } from "../lib"; +const logger = createLogger({ module: "weatherJobs" }); + export const weatherJob = createCronJob("weather", EVERY_10_MINUTES).withCallback(async () => { const weatherItems = await db.query.items.findMany({ where: eq(items.kind, "weather"), @@ -27,7 +30,7 @@ export const weatherJob = createCronJob("weather", EVERY_10_MINUTES).withCallbac }); await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: true }); } catch (error) { - logger.error("Failed to update weather", { id: item.id, error }); + logger.error(new ErrorWithMetadata("Failed to update weather", { id: item.id }, { cause: error })); } } }); diff --git a/packages/cron-jobs/src/lib/index.ts b/packages/cron-jobs/src/lib/index.ts index 28516fc53..898deebb3 100644 --- a/packages/cron-jobs/src/lib/index.ts +++ b/packages/cron-jobs/src/lib/index.ts @@ -1,24 +1,29 @@ +import { createLogger } from "@homarr/core/infrastructure/logs"; import { beforeCallbackAsync, onCallbackErrorAsync, onCallbackSuccessAsync } from "@homarr/cron-job-status/publisher"; import { createCronJobFunctions } from "@homarr/cron-jobs-core"; import type { Logger } from "@homarr/cron-jobs-core/logger"; -import { logger } from "@homarr/log"; import type { TranslationObject } from "@homarr/translation"; +const logger = createLogger({ module: "cronJobs" }); + class WinstonCronJobLogger implements Logger { - logDebug(message: string) { - logger.debug(message); + logDebug(message: string, metadata?: Record): void { + logger.debug(message, metadata); } - - logInfo(message: string) { - logger.info(message); + logInfo(message: string, metadata?: Record): void { + logger.info(message, metadata); } - - logError(error: unknown) { - logger.error(error); + logError(message: string, metadata?: Record): void; + logError(error: unknown): void; + logError(messageOrError: unknown, metadata?: Record): void { + if (typeof messageOrError === "string") { + logger.error(messageOrError, metadata); + return; + } + logger.error(messageOrError); } - - logWarning(message: string) { - logger.warn(message); + logWarning(message: string, metadata?: Record): void { + logger.warn(message, metadata); } } diff --git a/packages/db/collection.ts b/packages/db/collection.ts index 6a44b44c2..00e5aba18 100644 --- a/packages/db/collection.ts +++ b/packages/db/collection.ts @@ -1,9 +1,9 @@ import type { InferInsertModel } from "drizzle-orm"; import { objectEntries } from "@homarr/common"; +import { dbEnv } from "@homarr/core/infrastructure/db/env"; import type { HomarrDatabase, HomarrDatabaseMysql, HomarrDatabasePostgresql } from "./driver"; -import { env } from "./env"; import * as schema from "./schema"; type TableKey = { @@ -11,11 +11,11 @@ type TableKey = { }[keyof typeof schema]; export function isMysql(): boolean { - return env.DB_DRIVER === "mysql2"; + return dbEnv.DRIVER === "mysql2"; } export function isPostgresql(): boolean { - return env.DB_DRIVER === "node-postgres"; + return dbEnv.DRIVER === "node-postgres"; } export const createDbInsertCollectionForTransaction = ( @@ -66,7 +66,7 @@ export const createDbInsertCollectionWithoutTransaction = { - switch (env.DB_DRIVER) { + switch (dbEnv.DRIVER) { case "mysql2": case "node-postgres": // For mysql2 and node-postgres, we can use the async insertAllAsync method diff --git a/packages/db/configs/mysql.config.ts b/packages/db/configs/mysql.config.ts index 1eb3aa943..8954080e5 100644 --- a/packages/db/configs/mysql.config.ts +++ b/packages/db/configs/mysql.config.ts @@ -1,19 +1,20 @@ import type { Config } from "drizzle-kit"; -import { env } from "../env"; +import { DB_CASING } from "@homarr/core/infrastructure/db/constants"; +import { dbEnv } from "@homarr/core/infrastructure/db/env"; export default { dialect: "mysql", schema: "./schema", - casing: "snake_case", - dbCredentials: env.DB_URL - ? { url: env.DB_URL } + casing: DB_CASING, + dbCredentials: dbEnv.URL + ? { url: dbEnv.URL } : { - host: env.DB_HOST, - user: env.DB_USER, - password: env.DB_PASSWORD, - database: env.DB_NAME, - port: env.DB_PORT, + host: dbEnv.HOST, + port: dbEnv.PORT, + database: dbEnv.NAME, + user: dbEnv.USER, + password: dbEnv.PASSWORD, }, out: "./migrations/mysql", } satisfies Config; diff --git a/packages/db/configs/postgresql.config.ts b/packages/db/configs/postgresql.config.ts index a37115dfa..74cec4a5c 100644 --- a/packages/db/configs/postgresql.config.ts +++ b/packages/db/configs/postgresql.config.ts @@ -1,20 +1,21 @@ import type { Config } from "drizzle-kit"; -import { env } from "../env"; +import { DB_CASING } from "@homarr/core/infrastructure/db/constants"; +import { dbEnv } from "@homarr/core/infrastructure/db/env"; export default { dialect: "postgresql", schema: "./schema", - casing: "snake_case", + casing: DB_CASING, - dbCredentials: env.DB_URL - ? { url: env.DB_URL } + dbCredentials: dbEnv.URL + ? { url: dbEnv.URL } : { - host: env.DB_HOST, - port: env.DB_PORT, - user: env.DB_USER, - password: env.DB_PASSWORD, - database: env.DB_NAME, + host: dbEnv.HOST, + port: dbEnv.PORT, + database: dbEnv.NAME, + user: dbEnv.USER, + password: dbEnv.PASSWORD, }, out: "./migrations/postgresql", } satisfies Config; diff --git a/packages/db/configs/sqlite.config.ts b/packages/db/configs/sqlite.config.ts index 6b38860f2..b731928db 100644 --- a/packages/db/configs/sqlite.config.ts +++ b/packages/db/configs/sqlite.config.ts @@ -1,11 +1,12 @@ import type { Config } from "drizzle-kit"; -import { env } from "../env"; +import { DB_CASING } from "@homarr/core/infrastructure/db/constants"; +import { dbEnv } from "@homarr/core/infrastructure/db/env"; export default { dialect: "sqlite", schema: "./schema", - casing: "snake_case", - dbCredentials: { url: env.DB_URL }, + casing: DB_CASING, + dbCredentials: { url: dbEnv.URL }, out: "./migrations/sqlite", } satisfies Config; diff --git a/packages/db/driver.ts b/packages/db/driver.ts index 9ed5c899f..6c2f97178 100644 --- a/packages/db/driver.ts +++ b/packages/db/driver.ts @@ -1,113 +1,11 @@ -import type { Database as BetterSqlite3Connection } from "better-sqlite3"; -import Database from "better-sqlite3"; -import type { Logger } from "drizzle-orm"; import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; -import { drizzle as drizzleSqlite } from "drizzle-orm/better-sqlite3"; import type { MySql2Database } from "drizzle-orm/mysql2"; -import { drizzle as drizzleMysql } from "drizzle-orm/mysql2"; import type { NodePgDatabase } from "drizzle-orm/node-postgres"; -import { drizzle as drizzlePg } from "drizzle-orm/node-postgres"; -import type { Pool as MysqlConnectionPool } from "mysql2"; -import mysql from "mysql2"; -import { Pool as PostgresPool } from "pg"; -import { logger } from "@homarr/log"; - -import { env } from "./env"; -import * as mysqlSchema from "./schema/mysql"; -import * as pgSchema from "./schema/postgresql"; -import * as sqliteSchema from "./schema/sqlite"; +import type * as mysqlSchema from "./schema/mysql"; +import type * as pgSchema from "./schema/postgresql"; +import type * as sqliteSchema from "./schema/sqlite"; export type HomarrDatabase = BetterSQLite3Database; export type HomarrDatabaseMysql = MySql2Database; export type HomarrDatabasePostgresql = NodePgDatabase; - -const init = () => { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (!connection) { - switch (env.DB_DRIVER) { - case "mysql2": - initMySQL2(); - break; - case "node-postgres": - initNodePostgres(); - break; - default: - initBetterSqlite(); - break; - } - } -}; - -export let connection: BetterSqlite3Connection | MysqlConnectionPool | PostgresPool; -export let database: HomarrDatabase; - -class WinstonDrizzleLogger implements Logger { - logQuery(query: string, _: unknown[]): void { - logger.debug(`Executed SQL query: ${query}`); - } -} - -const initBetterSqlite = () => { - connection = new Database(env.DB_URL); - database = drizzleSqlite(connection, { - schema: sqliteSchema, - logger: new WinstonDrizzleLogger(), - casing: "snake_case", - }) as unknown as never; -}; - -const initMySQL2 = () => { - if (!env.DB_HOST) { - connection = mysql.createPool({ uri: env.DB_URL, maxIdle: 0, idleTimeout: 60000, enableKeepAlive: true }); - } else { - connection = mysql.createPool({ - host: env.DB_HOST, - database: env.DB_NAME, - port: env.DB_PORT, - user: env.DB_USER, - password: env.DB_PASSWORD, - maxIdle: 0, - idleTimeout: 60000, - enableKeepAlive: true, - }); - } - - database = drizzleMysql(connection, { - schema: mysqlSchema, - mode: "default", - logger: new WinstonDrizzleLogger(), - casing: "snake_case", - }) as unknown as HomarrDatabase; -}; - -const initNodePostgres = () => { - if (!env.DB_HOST) { - connection = new PostgresPool({ - connectionString: env.DB_URL, - max: 0, - idleTimeoutMillis: 60000, - allowExitOnIdle: false, - }); - } else { - connection = new PostgresPool({ - host: env.DB_HOST, - database: env.DB_NAME, - port: env.DB_PORT, - user: env.DB_USER, - password: env.DB_PASSWORD, - max: 0, - idleTimeoutMillis: 60000, - allowExitOnIdle: false, - }); - } - - database = drizzlePg({ - logger: new WinstonDrizzleLogger(), - schema: pgSchema, - casing: "snake_case", - client: connection, - }) as unknown as HomarrDatabase; -}; - -init(); diff --git a/packages/db/index.ts b/packages/db/index.ts index b796d2fa4..d32e46b34 100644 --- a/packages/db/index.ts +++ b/packages/db/index.ts @@ -1,12 +1,12 @@ -import Database from "better-sqlite3"; +import { createDb } from "@homarr/core/infrastructure/db"; -import { database } from "./driver"; +import { schema } from "./schema"; export * from "drizzle-orm"; - -export const db = database; - -export type Database = typeof db; export type { HomarrDatabaseMysql, HomarrDatabasePostgresql } from "./driver"; +export const db = createDb(schema); + +export type Database = typeof db; + export { handleDiffrentDbDriverOperationsAsync as handleTransactionsAsync } from "./transactions"; diff --git a/packages/db/migrations/custom/run-custom.ts b/packages/db/migrations/custom/run-custom.ts index 659a9d6c3..70bb5d415 100644 --- a/packages/db/migrations/custom/run-custom.ts +++ b/packages/db/migrations/custom/run-custom.ts @@ -1,7 +1,7 @@ import { applyCustomMigrationsAsync } from "."; -import { database } from "../../driver"; +import { db } from "../.."; -applyCustomMigrationsAsync(database) +applyCustomMigrationsAsync(db) .then(() => { console.log("Custom migrations applied successfully"); process.exit(0); diff --git a/packages/db/migrations/mysql/migrate.ts b/packages/db/migrations/mysql/migrate.ts index afca2d7d4..3ad588dd4 100644 --- a/packages/db/migrations/mysql/migrate.ts +++ b/packages/db/migrations/mysql/migrate.ts @@ -1,9 +1,8 @@ -import { drizzle } from "drizzle-orm/mysql2"; import { migrate } from "drizzle-orm/mysql2/migrator"; -import mysql from "mysql2"; + +import { createMysqlDb, createSharedDbConfig } from "@homarr/core/infrastructure/db"; import type { Database } from "../.."; -import { env } from "../../env"; import * as mysqlSchema from "../../schema/mysql"; import { applyCustomMigrationsAsync } from "../custom"; import { seedDataAsync } from "../seed"; @@ -11,23 +10,8 @@ import { seedDataAsync } from "../seed"; const migrationsFolder = process.argv[2] ?? "."; const migrateAsync = async () => { - const mysql2 = mysql.createConnection( - env.DB_URL - ? { uri: env.DB_URL } - : { - host: env.DB_HOST, - database: env.DB_NAME, - port: env.DB_PORT, - user: env.DB_USER, - password: env.DB_PASSWORD, - }, - ); - - const db = drizzle(mysql2, { - mode: "default", - schema: mysqlSchema, - casing: "snake_case", - }); + const config = createSharedDbConfig(mysqlSchema); + const db = createMysqlDb(config); await migrate(db, { migrationsFolder }); await seedDataAsync(db as unknown as Database); diff --git a/packages/db/migrations/postgresql/migrate.ts b/packages/db/migrations/postgresql/migrate.ts index 6e97f7a1b..afdbac1ed 100644 --- a/packages/db/migrations/postgresql/migrate.ts +++ b/packages/db/migrations/postgresql/migrate.ts @@ -1,9 +1,8 @@ -import { drizzle } from "drizzle-orm/node-postgres"; import { migrate } from "drizzle-orm/node-postgres/migrator"; -import { Pool } from "pg"; + +import { createPostgresDb, createSharedDbConfig } from "@homarr/core/infrastructure/db"; import type { Database } from "../.."; -import { env } from "../../env"; import * as pgSchema from "../../schema/postgresql"; import { applyCustomMigrationsAsync } from "../custom"; import { seedDataAsync } from "../seed"; @@ -11,23 +10,8 @@ import { seedDataAsync } from "../seed"; const migrationsFolder = process.argv[2] ?? "."; const migrateAsync = async () => { - const pool = new Pool( - env.DB_URL - ? { connectionString: env.DB_URL } - : { - host: env.DB_HOST, - database: env.DB_NAME, - port: env.DB_PORT, - user: env.DB_USER, - password: env.DB_PASSWORD, - }, - ); - - const db = drizzle({ - schema: pgSchema, - casing: "snake_case", - client: pool, - }); + const config = createSharedDbConfig(pgSchema); + const db = createPostgresDb(config); await migrate(db, { migrationsFolder }); await seedDataAsync(db as unknown as Database); diff --git a/packages/db/migrations/run-seed.ts b/packages/db/migrations/run-seed.ts index 7a0e346bc..8a834189e 100644 --- a/packages/db/migrations/run-seed.ts +++ b/packages/db/migrations/run-seed.ts @@ -1,7 +1,7 @@ -import { database } from "../driver"; +import { db } from ".."; import { seedDataAsync } from "./seed"; -seedDataAsync(database) +seedDataAsync(db) .then(() => { console.log("Seed complete"); process.exit(0); diff --git a/packages/db/migrations/sqlite/migrate.ts b/packages/db/migrations/sqlite/migrate.ts index 90d745746..e9c44d9f7 100644 --- a/packages/db/migrations/sqlite/migrate.ts +++ b/packages/db/migrations/sqlite/migrate.ts @@ -1,8 +1,7 @@ -import Database from "better-sqlite3"; -import { drizzle } from "drizzle-orm/better-sqlite3"; import { migrate } from "drizzle-orm/better-sqlite3/migrator"; -import { env } from "../../env"; +import { createSharedDbConfig, createSqliteDb } from "@homarr/core/infrastructure/db"; + import * as sqliteSchema from "../../schema/sqlite"; import { applyCustomMigrationsAsync } from "../custom"; import { seedDataAsync } from "../seed"; @@ -10,9 +9,8 @@ import { seedDataAsync } from "../seed"; const migrationsFolder = process.argv[2] ?? "."; const migrateAsync = async () => { - const sqlite = new Database(env.DB_URL.replace("file:", "")); - - const db = drizzle(sqlite, { schema: sqliteSchema, casing: "snake_case" }); + const config = createSharedDbConfig(sqliteSchema); + const db = createSqliteDb(config); migrate(db, { migrationsFolder }); diff --git a/packages/db/package.json b/packages/db/package.json index 159fdb648..7a244db4f 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -10,8 +10,7 @@ "./schema": "./schema/index.ts", "./test": "./test/index.ts", "./queries": "./queries/index.ts", - "./validationSchemas": "./validationSchemas.ts", - "./env": "./env.ts" + "./validationSchemas": "./validationSchemas.ts" }, "main": "./index.ts", "types": "./index.ts", @@ -47,16 +46,15 @@ "@homarr/common": "workspace:^0.1.0", "@homarr/core": "workspace:^0.1.0", "@homarr/definitions": "workspace:^0.1.0", - "@homarr/log": "workspace:^0.1.0", "@homarr/server-settings": "workspace:^0.1.0", - "@mantine/core": "^8.3.9", + "@mantine/core": "^8.3.10", "@paralleldrive/cuid2": "^3.1.0", - "@testcontainers/mysql": "^11.9.0", - "@testcontainers/postgresql": "^11.9.0", + "@testcontainers/mysql": "^11.10.0", + "@testcontainers/postgresql": "^11.10.0", "better-sqlite3": "^12.5.0", "dotenv": "^17.2.3", "drizzle-kit": "^0.31.8", - "drizzle-orm": "^0.45.0", + "drizzle-orm": "^0.45.1", "drizzle-zod": "^0.8.3", "mysql2": "3.15.3", "pg": "^8.16.3", @@ -67,10 +65,10 @@ "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", "@types/better-sqlite3": "7.6.13", - "@types/pg": "^8.15.6", + "@types/pg": "^8.16.0", "dotenv-cli": "^11.0.0", "esbuild": "^0.27.1", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "prettier": "^3.7.4", "tsx": "4.20.4", "typescript": "^5.9.3" diff --git a/packages/db/schema/index.ts b/packages/db/schema/index.ts index 52a1b9cdb..9929ccb86 100644 --- a/packages/db/schema/index.ts +++ b/packages/db/schema/index.ts @@ -1,20 +1,19 @@ import type { InferSelectModel } from "drizzle-orm"; -import { env } from "../env"; +import { createSchema } from "@homarr/core/infrastructure/db"; + import * as mysqlSchema from "./mysql"; import * as pgSchema from "./postgresql"; import * as sqliteSchema from "./sqlite"; export type PostgreSqlSchema = typeof pgSchema; export type MySqlSchema = typeof mysqlSchema; -type Schema = typeof sqliteSchema; -const schema = - env.DB_DRIVER === "mysql2" - ? (mysqlSchema as unknown as Schema) - : env.DB_DRIVER === "node-postgres" - ? (pgSchema as unknown as Schema) - : sqliteSchema; +export const schema = createSchema({ + "better-sqlite3": () => sqliteSchema, + mysql2: () => mysqlSchema, + "node-postgres": () => pgSchema, +}); // Sadly we can't use export * from here as we have multiple possible exports export const { diff --git a/packages/db/schema/mysql.ts b/packages/db/schema/mysql.ts index 2a0b042e7..edbcec281 100644 --- a/packages/db/schema/mysql.ts +++ b/packages/db/schema/mysql.ts @@ -46,6 +46,8 @@ const customBlob = customType<{ data: Buffer }>({ }, }); +export * from "@homarr/core/infrastructure/certificates/hostnames/db/mysql"; + export const apiKeys = mysqlTable("apiKey", { id: varchar({ length: 64 }).notNull().primaryKey(), apiKey: text().notNull(), @@ -495,20 +497,6 @@ export const onboarding = mysqlTable("onboarding", { previousStep: varchar({ length: 64 }).$type(), }); -export const trustedCertificateHostnames = mysqlTable( - "trusted_certificate_hostname", - { - hostname: varchar({ length: 256 }).notNull(), - thumbprint: varchar({ length: 128 }).notNull(), - certificate: text().notNull(), - }, - (table) => ({ - compoundKey: primaryKey({ - columns: [table.hostname, table.thumbprint], - }), - }), -); - export const cronJobConfigurations = mysqlTable("cron_job_configuration", { name: varchar({ length: 256 }).notNull().primaryKey(), cronExpression: varchar({ length: 32 }).notNull(), diff --git a/packages/db/schema/postgresql.ts b/packages/db/schema/postgresql.ts index 5a48ce4e8..e41d72f75 100644 --- a/packages/db/schema/postgresql.ts +++ b/packages/db/schema/postgresql.ts @@ -45,6 +45,8 @@ const customBlob = customType<{ data: Buffer }>({ }, }); +export * from "@homarr/core/infrastructure/certificates/hostnames/db/postgresql"; + export const apiKeys = pgTable("apiKey", { id: varchar({ length: 64 }).notNull().primaryKey(), apiKey: text().notNull(), @@ -494,20 +496,6 @@ export const onboarding = pgTable("onboarding", { previousStep: varchar({ length: 64 }).$type(), }); -export const trustedCertificateHostnames = pgTable( - "trusted_certificate_hostname", - { - hostname: varchar({ length: 256 }).notNull(), - thumbprint: varchar({ length: 128 }).notNull(), - certificate: text().notNull(), - }, - (table) => ({ - compoundKey: primaryKey({ - columns: [table.hostname, table.thumbprint], - }), - }), -); - export const cronJobConfigurations = pgTable("cron_job_configuration", { name: varchar({ length: 256 }).notNull().primaryKey(), cronExpression: varchar({ length: 32 }).notNull(), diff --git a/packages/db/schema/sqlite.ts b/packages/db/schema/sqlite.ts index e7b6d4d9f..78ec4ac9c 100644 --- a/packages/db/schema/sqlite.ts +++ b/packages/db/schema/sqlite.ts @@ -28,6 +28,8 @@ import type { WidgetKind, } from "@homarr/definitions"; +export * from "@homarr/core/infrastructure/certificates/hostnames/db/sqlite"; + export const apiKeys = sqliteTable("apiKey", { id: text().notNull().primaryKey(), apiKey: text().notNull(), @@ -480,20 +482,6 @@ export const onboarding = sqliteTable("onboarding", { previousStep: text().$type(), }); -export const trustedCertificateHostnames = sqliteTable( - "trusted_certificate_hostname", - { - hostname: text().notNull(), - thumbprint: text().notNull(), - certificate: text().notNull(), - }, - (table) => ({ - compoundKey: primaryKey({ - columns: [table.hostname, table.thumbprint], - }), - }), -); - export const cronJobConfigurations = sqliteTable("cron_job_configuration", { name: text().notNull().primaryKey(), cronExpression: text().notNull(), diff --git a/packages/db/test/db-mock.ts b/packages/db/test/db-mock.ts index 176a27534..4001534a6 100644 --- a/packages/db/test/db-mock.ts +++ b/packages/db/test/db-mock.ts @@ -2,11 +2,13 @@ import Database from "better-sqlite3"; import { drizzle } from "drizzle-orm/better-sqlite3"; import { migrate } from "drizzle-orm/better-sqlite3/migrator"; +import { DB_CASING } from "@homarr/core/infrastructure/db/constants"; + import * as sqliteSchema from "../schema/sqlite"; export const createDb = (debug?: boolean) => { const sqlite = new Database(":memory:"); - const db = drizzle(sqlite, { schema: sqliteSchema, logger: debug, casing: "snake_case" }); + const db = drizzle(sqlite, { schema: sqliteSchema, logger: debug, casing: DB_CASING }); migrate(db, { migrationsFolder: "./packages/db/migrations/sqlite", }); diff --git a/packages/db/test/mysql-migration.spec.ts b/packages/db/test/mysql-migration.spec.ts index 8b178664b..a235f00fc 100644 --- a/packages/db/test/mysql-migration.spec.ts +++ b/packages/db/test/mysql-migration.spec.ts @@ -5,6 +5,8 @@ import { migrate } from "drizzle-orm/mysql2/migrator"; import mysql from "mysql2"; import { describe, test } from "vitest"; +import { DB_CASING } from "@homarr/core/infrastructure/db/constants"; + import * as mysqlSchema from "../schema/mysql"; describe("Mysql Migration", () => { @@ -22,7 +24,7 @@ describe("Mysql Migration", () => { const database = drizzle(connection, { schema: mysqlSchema, mode: "default", - casing: "snake_case", + casing: DB_CASING, }); // Run migrations and check if it works diff --git a/packages/db/test/postgresql-migration.spec.ts b/packages/db/test/postgresql-migration.spec.ts index 40dd28e46..c7d4ce4ee 100644 --- a/packages/db/test/postgresql-migration.spec.ts +++ b/packages/db/test/postgresql-migration.spec.ts @@ -5,6 +5,8 @@ import { migrate } from "drizzle-orm/node-postgres/migrator"; import { Pool } from "pg"; import { describe, test } from "vitest"; +import { DB_CASING } from "@homarr/core/infrastructure/db/constants"; + import * as pgSchema from "../schema/postgresql"; describe("PostgreSql Migration", () => { @@ -26,7 +28,7 @@ describe("PostgreSql Migration", () => { const database = drizzle({ schema: pgSchema, - casing: "snake_case", + casing: DB_CASING, client: pool, }); diff --git a/packages/definitions/package.json b/packages/definitions/package.json index ccb47af36..c6c4ccbf2 100644 --- a/packages/definitions/package.json +++ b/packages/definitions/package.json @@ -24,14 +24,14 @@ "prettier": "@homarr/prettier-config", "dependencies": { "@homarr/common": "workspace:^0.1.0", - "fast-xml-parser": "^5.3.2", + "fast-xml-parser": "^5.3.3", "zod": "^4.1.13" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "tsx": "4.20.4", "typescript": "^5.9.3" } diff --git a/packages/docker/package.json b/packages/docker/package.json index b6b91da4e..bd7bc1727 100644 --- a/packages/docker/package.json +++ b/packages/docker/package.json @@ -33,7 +33,7 @@ "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", "@types/dockerode": "^3.3.47", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/form/package.json b/packages/form/package.json index 32b8c388d..8da46fa2a 100644 --- a/packages/form/package.json +++ b/packages/form/package.json @@ -26,7 +26,7 @@ "@homarr/common": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0", - "@mantine/form": "^8.3.9", + "@mantine/form": "^8.3.10", "mantine-form-zod-resolver": "^1.3.0", "zod": "^4.1.13" }, @@ -34,7 +34,7 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/forms-collection/package.json b/packages/forms-collection/package.json index 311a4ea1d..39c581dd3 100644 --- a/packages/forms-collection/package.json +++ b/packages/forms-collection/package.json @@ -30,15 +30,15 @@ "@homarr/translation": "workspace:^0.1.0", "@homarr/ui": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0", - "@mantine/core": "^8.3.9", - "react": "19.2.1", + "@mantine/core": "^8.3.10", + "react": "19.2.3", "zod": "^4.1.13" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/icons/package.json b/packages/icons/package.json index 682bfa651..7460e3706 100644 --- a/packages/icons/package.json +++ b/packages/icons/package.json @@ -24,14 +24,14 @@ "prettier": "@homarr/prettier-config", "dependencies": { "@homarr/common": "workspace:^0.1.0", - "@homarr/db": "workspace:^0.1.0", - "@homarr/log": "workspace:^0.1.0" + "@homarr/core": "workspace:^0.1.0", + "@homarr/db": "workspace:^0.1.0" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/icons/src/repositories/github.icon-repository.ts b/packages/icons/src/repositories/github.icon-repository.ts index 2152a450b..8f6e55dc8 100644 --- a/packages/icons/src/repositories/github.icon-repository.ts +++ b/packages/icons/src/repositories/github.icon-repository.ts @@ -1,6 +1,6 @@ import { parse } from "path"; -import { fetchWithTimeout } from "@homarr/common"; +import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout"; import type { IconRepositoryLicense } from "../types/icon-repository-license"; import type { RepositoryIconGroup } from "../types/repository-icon-group"; @@ -23,7 +23,7 @@ export class GitHubIconRepository extends IconRepository { throw new Error("Repository URLs are required for this repository"); } - const response = await fetchWithTimeout(this.repositoryIndexingUrl); + const response = await fetchWithTimeoutAsync(this.repositoryIndexingUrl); const listOfFiles = (await response.json()) as GitHubApiResponse; return { diff --git a/packages/icons/src/repositories/icon-repository.ts b/packages/icons/src/repositories/icon-repository.ts index 525ca1422..454df71e6 100644 --- a/packages/icons/src/repositories/icon-repository.ts +++ b/packages/icons/src/repositories/icon-repository.ts @@ -1,8 +1,11 @@ -import { logger } from "@homarr/log"; +import { createLogger } from "@homarr/core/infrastructure/logs"; +import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; import type { IconRepositoryLicense } from "../types/icon-repository-license"; import type { RepositoryIconGroup } from "../types/repository-icon-group"; +const logger = createLogger({ module: "iconRepository" }); + export abstract class IconRepository { protected readonly allowedImageFileTypes = [".png", ".svg", ".jpeg"]; @@ -19,7 +22,9 @@ export abstract class IconRepository { try { return await this.getAllIconsInternalAsync(); } catch (err) { - logger.error(`Unable to request icons from repository "${this.slug}": ${JSON.stringify(err)}`); + logger.error( + new ErrorWithMetadata("Unable to request icons from repository", { slug: this.slug }, { cause: err }), + ); return { success: false, icons: [], diff --git a/packages/icons/src/repositories/jsdelivr.icon-repository.ts b/packages/icons/src/repositories/jsdelivr.icon-repository.ts index 2b9bd944b..acff85923 100644 --- a/packages/icons/src/repositories/jsdelivr.icon-repository.ts +++ b/packages/icons/src/repositories/jsdelivr.icon-repository.ts @@ -1,6 +1,6 @@ import { parse } from "path"; -import { fetchWithTimeout } from "@homarr/common"; +import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout"; import type { IconRepositoryLicense } from "../types/icon-repository-license"; import type { RepositoryIconGroup } from "../types/repository-icon-group"; @@ -19,7 +19,7 @@ export class JsdelivrIconRepository extends IconRepository { } protected async getAllIconsInternalAsync(): Promise { - const response = await fetchWithTimeout(this.repositoryIndexingUrl); + const response = await fetchWithTimeoutAsync(this.repositoryIndexingUrl); const listOfFiles = (await response.json()) as JsdelivrApiResponse; return { diff --git a/packages/image-proxy/package.json b/packages/image-proxy/package.json index e46fda9f9..f6ea7ccf6 100644 --- a/packages/image-proxy/package.json +++ b/packages/image-proxy/package.json @@ -22,9 +22,8 @@ }, "prettier": "@homarr/prettier-config", "dependencies": { - "@homarr/certificates": "workspace:^0.1.0", "@homarr/common": "workspace:^0.1.0", - "@homarr/log": "workspace:^0.1.0", + "@homarr/core": "workspace:^0.1.0", "@homarr/redis": "workspace:^0.1.0", "bcrypt": "^6.0.0" }, @@ -33,7 +32,7 @@ "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", "@types/bcrypt": "6.0.0", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/image-proxy/src/index.ts b/packages/image-proxy/src/index.ts index 19f66e608..736a64dcb 100644 --- a/packages/image-proxy/src/index.ts +++ b/packages/image-proxy/src/index.ts @@ -1,11 +1,14 @@ import bcrypt from "bcrypt"; -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; import { createId } from "@homarr/common"; import { decryptSecret, encryptSecret } from "@homarr/common/server"; -import { logger } from "@homarr/log"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; +import { createLogger } from "@homarr/core/infrastructure/logs"; +import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; import { createGetSetChannel } from "@homarr/redis"; +const logger = createLogger({ module: "imageProxy" }); + const createHashChannel = (hash: `${string}.${string}`) => createGetSetChannel(`image-proxy:hash:${hash}`); const createUrlByIdChannel = (id: string) => createGetSetChannel<{ @@ -25,7 +28,7 @@ export class ImageProxy { } const salt = await bcrypt.genSalt(10); - logger.debug(`Generated new salt for image proxy salt="${salt}"`); + logger.debug("Generated new salt for image proxy", { salt }); ImageProxy.salt = salt; await saltChannel.setAsync(salt); return salt; @@ -34,9 +37,11 @@ export class ImageProxy { public async createImageAsync(url: string, headers?: Record): Promise { const existingId = await this.getExistingIdAsync(url, headers); if (existingId) { - logger.debug( - `Image already exists in the proxy id="${existingId}" url="${this.redactUrl(url)}" headers="${this.redactHeaders(headers ?? null)}"`, - ); + logger.debug("Image already exists in the proxy", { + id: existingId, + url: this.redactUrl(url), + headers: this.redactHeaders(headers ?? null), + }); return this.createImageUrl(existingId); } @@ -59,15 +64,25 @@ export class ImageProxy { const proxyUrl = this.createImageUrl(id); if (!response.ok) { logger.error( - `Failed to fetch image id="${id}" url="${this.redactUrl(urlAndHeaders.url)}" headers="${this.redactHeaders(urlAndHeaders.headers)}" proxyUrl="${proxyUrl}" statusCode="${response.status}"`, + new ErrorWithMetadata("Failed to fetch image", { + id, + url: this.redactUrl(urlAndHeaders.url), + headers: this.redactHeaders(urlAndHeaders.headers), + proxyUrl, + statusCode: response.status, + }), ); return null; } const blob = (await response.blob()) as Blob; - logger.debug( - `Forwarding image succeeded id="${id}" url="${this.redactUrl(urlAndHeaders.url)}" headers="${this.redactHeaders(urlAndHeaders.headers)}" proxyUrl="${proxyUrl} size="${(blob.size / 1024).toFixed(1)}KB"`, - ); + logger.debug("Forwarding image succeeded", { + id, + url: this.redactUrl(urlAndHeaders.url), + headers: this.redactHeaders(urlAndHeaders.headers), + proxyUrl, + size: `${(blob.size / 1024).toFixed(1)}KB`, + }); return blob; } @@ -80,7 +95,7 @@ export class ImageProxy { const urlHeaderChannel = createUrlByIdChannel(id); const urlHeader = await urlHeaderChannel.getAsync(); if (!urlHeader) { - logger.warn(`Image not found in the proxy id="${id}"`); + logger.warn("Image not found in the proxy", { id }); return null; } @@ -112,9 +127,11 @@ export class ImageProxy { }); await hashChannel.setAsync(id); - logger.debug( - `Stored image in the proxy id="${id}" url="${this.redactUrl(url)}" headers="${this.redactHeaders(headers ?? null)}"`, - ); + logger.debug("Stored image in the proxy", { + id, + url: this.redactUrl(url), + headers: this.redactHeaders(headers ?? null), + }); } private redactUrl(url: string): string { diff --git a/packages/integrations/package.json b/packages/integrations/package.json index 401a86133..71bada623 100644 --- a/packages/integrations/package.json +++ b/packages/integrations/package.json @@ -29,12 +29,11 @@ "@ctrl/qbittorrent": "^9.11.0", "@ctrl/transmission": "^7.4.0", "@gitbeaker/rest": "^43.8.0", - "@homarr/certificates": "workspace:^0.1.0", "@homarr/common": "workspace:^0.1.0", + "@homarr/core": "workspace:^0.1.0", "@homarr/db": "workspace:^0.1.0", "@homarr/definitions": "workspace:^0.1.0", "@homarr/image-proxy": "workspace:^0.1.0", - "@homarr/log": "workspace:^0.1.0", "@homarr/node-unifi": "^2.6.0", "@homarr/redis": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0", @@ -57,7 +56,7 @@ "@homarr/tsconfig": "workspace:^0.1.0", "@types/node-unifi": "^2.5.1", "@types/xml2js": "^0.4.14", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/integrations/src/adguard-home/adguard-home-integration.ts b/packages/integrations/src/adguard-home/adguard-home-integration.ts index 227ffdbdb..6292617bb 100644 --- a/packages/integrations/src/adguard-home/adguard-home-integration.ts +++ b/packages/integrations/src/adguard-home/adguard-home-integration.ts @@ -1,5 +1,5 @@ -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; import { ParseError } from "@homarr/common/server"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; import type { IntegrationTestingInput } from "../base/integration"; import { Integration } from "../base/integration"; diff --git a/packages/integrations/src/base/errors/decorator.ts b/packages/integrations/src/base/errors/decorator.ts index c5a200162..3aac2eff7 100644 --- a/packages/integrations/src/base/errors/decorator.ts +++ b/packages/integrations/src/base/errors/decorator.ts @@ -1,5 +1,5 @@ import { isFunction } from "@homarr/common"; -import { logger } from "@homarr/log"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import type { Integration } from "../integration"; import type { IIntegrationErrorHandler } from "./handler"; @@ -8,9 +8,7 @@ import { IntegrationError } from "./integration-error"; import { IntegrationUnknownError } from "./integration-unknown-error"; import { integrationJsonParseErrorHandler, integrationZodParseErrorHandler } from "./parse"; -const localLogger = logger.child({ - module: "HandleIntegrationErrors", -}); +const logger = createLogger({ module: "handleIntegrationErrors" }); // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-explicit-any type AbstractConstructor = abstract new (...args: any[]) => T; @@ -59,7 +57,7 @@ export const HandleIntegrationErrors = (errorHandlers: IIntegrationErrorHandler[ } // If the error was handled and should be thrown again, throw it - localLogger.debug("Unhandled error in integration", { + logger.debug("Unhandled error in integration", { error: error instanceof Error ? `${error.name}: ${error.message}` : undefined, integrationName: this.publicIntegration.name, }); diff --git a/packages/integrations/src/base/integration.ts b/packages/integrations/src/base/integration.ts index 9b07a7f6d..ec6c7e4b5 100644 --- a/packages/integrations/src/base/integration.ts +++ b/packages/integrations/src/base/integration.ts @@ -3,8 +3,8 @@ import type { AxiosInstance } from "axios"; import type { Dispatcher } from "undici"; import { fetch as undiciFetch } from "undici"; -import { createAxiosCertificateInstanceAsync, createCertificateAgentAsync } from "@homarr/certificates/server"; import { removeTrailingSlash } from "@homarr/common"; +import { createAxiosCertificateInstanceAsync, createCertificateAgentAsync } from "@homarr/core/infrastructure/http"; import type { IntegrationSecretKind } from "@homarr/definitions"; import { HandleIntegrationErrors } from "./errors/decorator"; diff --git a/packages/integrations/src/base/session-store.ts b/packages/integrations/src/base/session-store.ts index 58e4dad46..468440a0e 100644 --- a/packages/integrations/src/base/session-store.ts +++ b/packages/integrations/src/base/session-store.ts @@ -1,10 +1,11 @@ import superjson from "superjson"; import { decryptSecret, encryptSecret } from "@homarr/common/server"; -import { logger } from "@homarr/log"; +import { createLogger } from "@homarr/core/infrastructure/logs"; +import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; import { createGetSetChannel } from "@homarr/redis"; -const localLogger = logger.child({ module: "SessionStore" }); +const logger = createLogger({ module: "sessionStore" }); export const createSessionStore = (integration: { id: string }) => { const channelName = `session-store:${integration.id}`; @@ -12,26 +13,26 @@ export const createSessionStore = (integration: { id: string }) => { return { async getAsync() { - localLogger.debug("Getting session from store", { store: channelName }); + logger.debug("Getting session from store", { store: channelName }); const value = await channel.getAsync(); if (!value) return null; try { return superjson.parse(decryptSecret(value)); } catch (error) { - localLogger.warn("Failed to load session", { store: channelName, error }); + logger.warn("Failed to load session", { store: channelName, error }); return null; } }, async setAsync(value: TValue) { - localLogger.debug("Updating session in store", { store: channelName }); + logger.debug("Updating session in store", { store: channelName }); try { await channel.setAsync(encryptSecret(superjson.stringify(value))); } catch (error) { - localLogger.error("Failed to save session", { store: channelName, error }); + logger.error(new ErrorWithMetadata("Failed to save session", { store: channelName }, { cause: error })); } }, async clearAsync() { - localLogger.debug("Cleared session in store", { store: channelName }); + logger.debug("Cleared session in store", { store: channelName }); await channel.removeAsync(); }, }; diff --git a/packages/integrations/src/base/test-connection/test-connection-service.ts b/packages/integrations/src/base/test-connection/test-connection-service.ts index 16bf664b7..4874d706d 100644 --- a/packages/integrations/src/base/test-connection/test-connection-service.ts +++ b/packages/integrations/src/base/test-connection/test-connection-service.ts @@ -1,13 +1,13 @@ import type { X509Certificate } from "node:crypto"; import tls from "node:tls"; +import { getPortFromUrl } from "@homarr/common"; import { - createCustomCheckServerIdentity, getAllTrustedCertificatesAsync, getTrustedCertificateHostnamesAsync, -} from "@homarr/certificates/server"; -import { getPortFromUrl } from "@homarr/common"; -import { logger } from "@homarr/log"; +} from "@homarr/core/infrastructure/certificates"; +import { createCustomCheckServerIdentity } from "@homarr/core/infrastructure/http"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import type { IntegrationRequestErrorOfType } from "../errors/http/integration-request-error"; import { IntegrationRequestError } from "../errors/http/integration-request-error"; @@ -15,8 +15,8 @@ import { IntegrationError } from "../errors/integration-error"; import type { AnyTestConnectionError } from "./test-connection-error"; import { TestConnectionError } from "./test-connection-error"; -const localLogger = logger.child({ - module: "TestConnectionService", +const logger = createLogger({ + module: "testConnectionService", }); export type TestingResult = @@ -36,7 +36,7 @@ export class TestConnectionService { constructor(private url: URL) {} public async handleAsync(testingCallbackAsync: AsyncTestingCallback) { - localLogger.debug("Testing connection", { + logger.debug("Testing connection", { url: this.url.toString(), }); @@ -72,14 +72,14 @@ export class TestConnectionService { }); if (testingResult.success) { - localLogger.debug("Testing connection succeeded", { + logger.debug("Testing connection succeeded", { url: this.url.toString(), }); return testingResult; } - localLogger.debug("Testing connection failed", { + logger.debug("Testing connection failed", { url: this.url.toString(), error: `${testingResult.error.name}: ${testingResult.error.message}`, }); @@ -124,7 +124,7 @@ export class TestConnectionService { const x509 = socket.getPeerX509Certificate(); socket.destroy(); - localLogger.debug("Fetched certificate", { + logger.debug("Fetched certificate", { url: this.url.toString(), subject: x509?.subject, issuer: x509?.issuer, diff --git a/packages/integrations/src/codeberg/codeberg-integration.ts b/packages/integrations/src/codeberg/codeberg-integration.ts index ea6543cbb..2396a81e0 100644 --- a/packages/integrations/src/codeberg/codeberg-integration.ts +++ b/packages/integrations/src/codeberg/codeberg-integration.ts @@ -1,7 +1,7 @@ import type { RequestInit, Response } from "undici"; -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; -import { logger } from "@homarr/log"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import type { IntegrationTestingInput } from "../base/integration"; import { Integration } from "../base/integration"; @@ -15,7 +15,7 @@ import type { } from "../interfaces/releases-providers/releases-providers-types"; import { detailsResponseSchema, releasesResponseSchema } from "./codeberg-schemas"; -const localLogger = logger.child({ module: "CodebergIntegration" }); +const logger = createLogger({ module: "codebergIntegration" }); export class CodebergIntegration extends Integration implements ReleasesProviderIntegration { private async withHeadersAsync(callback: (headers: RequestInit["headers"]) => Promise): Promise { @@ -45,10 +45,9 @@ export class CodebergIntegration extends Integration implements ReleasesProvider private parseIdentifier(identifier: string) { const [owner, name] = identifier.split("/"); if (!owner || !name) { - localLogger.warn( - `Invalid identifier format. Expected 'owner/name', for ${identifier} with Codeberg integration`, - { identifier }, - ); + logger.warn("Invalid identifier format. Expected 'owner/name', for identifier", { + identifier, + }); return null; } return { owner, name }; @@ -109,7 +108,7 @@ export class CodebergIntegration extends Integration implements ReleasesProvider }); if (!response.ok) { - localLogger.warn(`Failed to get details response for ${owner}/${name} with Codeberg integration`, { + logger.warn("Failed to get details", { owner, name, error: response.statusText, @@ -122,7 +121,7 @@ export class CodebergIntegration extends Integration implements ReleasesProvider const { data, success, error } = detailsResponseSchema.safeParse(responseJson); if (!success) { - localLogger.warn(`Failed to parse details response for ${owner}/${name} with Codeberg integration`, { + logger.warn("Failed to parse details", { owner, name, error, diff --git a/packages/integrations/src/dashdot/dashdot-integration.ts b/packages/integrations/src/dashdot/dashdot-integration.ts index bf58feb40..2efb47c16 100644 --- a/packages/integrations/src/dashdot/dashdot-integration.ts +++ b/packages/integrations/src/dashdot/dashdot-integration.ts @@ -5,7 +5,7 @@ import "@homarr/redis"; import dayjs from "dayjs"; import { z } from "zod/v4"; -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; import { createChannelEventHistoryOld } from "../../../redis/src/lib/channel"; import type { IntegrationTestingInput } from "../base/integration"; diff --git a/packages/integrations/src/docker-hub/docker-hub-integration.ts b/packages/integrations/src/docker-hub/docker-hub-integration.ts index d0e99c0e6..b9e9ea09c 100644 --- a/packages/integrations/src/docker-hub/docker-hub-integration.ts +++ b/packages/integrations/src/docker-hub/docker-hub-integration.ts @@ -1,8 +1,8 @@ import type { fetch, RequestInit, Response } from "undici"; -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; import { ResponseError } from "@homarr/common/server"; -import { logger } from "@homarr/log"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import type { IntegrationInput, IntegrationTestingInput } from "../base/integration"; import { Integration } from "../base/integration"; @@ -18,7 +18,7 @@ import type { } from "../interfaces/releases-providers/releases-providers-types"; import { accessTokenResponseSchema, detailsResponseSchema, releasesResponseSchema } from "./docker-hub-schemas"; -const localLogger = logger.child({ module: "DockerHubIntegration" }); +const logger = createLogger({ module: "dockerHubIntegration" }); export class DockerHubIntegration extends Integration implements ReleasesProviderIntegration { private readonly sessionStore: SessionStore; @@ -35,7 +35,7 @@ export class DockerHubIntegration extends Integration implements ReleasesProvide const storedSession = await this.sessionStore.getAsync(); if (storedSession) { - localLogger.debug("Using stored session for request", { integrationId: this.integration.id }); + logger.debug("Using stored session for request", { integrationId: this.integration.id }); const response = await callback({ Authorization: `Bearer ${storedSession}`, }); @@ -43,7 +43,7 @@ export class DockerHubIntegration extends Integration implements ReleasesProvide return response; } - localLogger.debug("Session expired, getting new session", { integrationId: this.integration.id }); + logger.debug("Session expired, getting new session", { integrationId: this.integration.id }); } const accessToken = await this.getSessionAsync(); @@ -57,10 +57,10 @@ export class DockerHubIntegration extends Integration implements ReleasesProvide const hasAuth = this.hasSecretValue("username") && this.hasSecretValue("personalAccessToken"); if (hasAuth) { - localLogger.debug("Testing DockerHub connection with authentication", { integrationId: this.integration.id }); + logger.debug("Testing DockerHub connection with authentication", { integrationId: this.integration.id }); await this.getSessionAsync(input.fetchAsync); } else { - localLogger.debug("Testing DockerHub connection without authentication", { integrationId: this.integration.id }); + logger.debug("Testing DockerHub connection without authentication", { integrationId: this.integration.id }); const response = await input.fetchAsync(this.url("/v2/repositories/library")); if (!response.ok) { return TestConnectionError.StatusResult(response); @@ -76,7 +76,7 @@ export class DockerHubIntegration extends Integration implements ReleasesProvide if (!identifier.includes("/")) return { owner: "", name: identifier }; const [owner, name] = identifier.split("/"); if (!owner || !name) { - localLogger.warn(`Invalid identifier format. Expected 'owner/name' or 'name', for ${identifier} on DockerHub`, { + logger.warn("Invalid identifier format. Expected 'owner/name' or 'name', for identifier", { identifier, }); return null; @@ -137,7 +137,7 @@ export class DockerHubIntegration extends Integration implements ReleasesProvide }); if (!response.ok) { - localLogger.warn(`Failed to get details response for ${relativeUrl} with DockerHub integration`, { + logger.warn("Failed to get details response", { relativeUrl, error: response.statusText, }); @@ -149,7 +149,7 @@ export class DockerHubIntegration extends Integration implements ReleasesProvide const { data, success, error } = detailsResponseSchema.safeParse(responseJson); if (!success) { - localLogger.warn(`Failed to parse details response for ${relativeUrl} with DockerHub integration`, { + logger.warn("Failed to parse details response", { relativeUrl, error, }); @@ -183,7 +183,7 @@ export class DockerHubIntegration extends Integration implements ReleasesProvide throw new ResponseError({ status: 401, url: response.url }); } - localLogger.info("Received session successfully", { integrationId: this.integration.id }); + logger.info("Received session successfully", { integrationId: this.integration.id }); return result.access_token; } diff --git a/packages/integrations/src/download-client/aria2/aria2-integration.ts b/packages/integrations/src/download-client/aria2/aria2-integration.ts index 8c8482a94..447e33742 100644 --- a/packages/integrations/src/download-client/aria2/aria2-integration.ts +++ b/packages/integrations/src/download-client/aria2/aria2-integration.ts @@ -1,8 +1,8 @@ import path from "path"; import type { fetch as undiciFetch } from "undici"; -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; import { ResponseError } from "@homarr/common/server"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; import { Integration } from "../../base/integration"; import type { IntegrationTestingInput } from "../../base/integration"; diff --git a/packages/integrations/src/download-client/deluge/deluge-integration.ts b/packages/integrations/src/download-client/deluge/deluge-integration.ts index a3310777a..0ed32127b 100644 --- a/packages/integrations/src/download-client/deluge/deluge-integration.ts +++ b/packages/integrations/src/download-client/deluge/deluge-integration.ts @@ -2,7 +2,7 @@ import { Deluge } from "@ctrl/deluge"; import dayjs from "dayjs"; import type { Dispatcher } from "undici"; -import { createCertificateAgentAsync } from "@homarr/certificates/server"; +import { createCertificateAgentAsync } from "@homarr/core/infrastructure/http"; import { HandleIntegrationErrors } from "../../base/errors/decorator"; import { integrationOFetchHttpErrorHandler } from "../../base/errors/http"; diff --git a/packages/integrations/src/download-client/nzbget/nzbget-integration.ts b/packages/integrations/src/download-client/nzbget/nzbget-integration.ts index 3b799f835..1d5c66429 100644 --- a/packages/integrations/src/download-client/nzbget/nzbget-integration.ts +++ b/packages/integrations/src/download-client/nzbget/nzbget-integration.ts @@ -1,8 +1,8 @@ import dayjs from "dayjs"; import type { fetch as undiciFetch } from "undici"; -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; import { ResponseError } from "@homarr/common/server"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; import { Integration } from "../../base/integration"; import type { IntegrationTestingInput } from "../../base/integration"; diff --git a/packages/integrations/src/download-client/qbittorrent/qbittorrent-integration.ts b/packages/integrations/src/download-client/qbittorrent/qbittorrent-integration.ts index 1634f39a0..1076bca5e 100644 --- a/packages/integrations/src/download-client/qbittorrent/qbittorrent-integration.ts +++ b/packages/integrations/src/download-client/qbittorrent/qbittorrent-integration.ts @@ -2,7 +2,7 @@ import { QBittorrent } from "@ctrl/qbittorrent"; import dayjs from "dayjs"; import type { Dispatcher } from "undici"; -import { createCertificateAgentAsync } from "@homarr/certificates/server"; +import { createCertificateAgentAsync } from "@homarr/core/infrastructure/http"; import { HandleIntegrationErrors } from "../../base/errors/decorator"; import { integrationOFetchHttpErrorHandler } from "../../base/errors/http"; diff --git a/packages/integrations/src/download-client/sabnzbd/sabnzbd-integration.ts b/packages/integrations/src/download-client/sabnzbd/sabnzbd-integration.ts index 42abc58b1..69352ca02 100644 --- a/packages/integrations/src/download-client/sabnzbd/sabnzbd-integration.ts +++ b/packages/integrations/src/download-client/sabnzbd/sabnzbd-integration.ts @@ -2,8 +2,8 @@ import dayjs from "dayjs"; import duration from "dayjs/plugin/duration"; import type { fetch as undiciFetch } from "undici"; -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; import { ResponseError } from "@homarr/common/server"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; import { Integration } from "../../base/integration"; import type { IntegrationTestingInput } from "../../base/integration"; diff --git a/packages/integrations/src/download-client/transmission/transmission-integration.ts b/packages/integrations/src/download-client/transmission/transmission-integration.ts index fe148dd55..7b579b1bc 100644 --- a/packages/integrations/src/download-client/transmission/transmission-integration.ts +++ b/packages/integrations/src/download-client/transmission/transmission-integration.ts @@ -2,7 +2,7 @@ import { Transmission } from "@ctrl/transmission"; import dayjs from "dayjs"; import type { Dispatcher } from "undici"; -import { createCertificateAgentAsync } from "@homarr/certificates/server"; +import { createCertificateAgentAsync } from "@homarr/core/infrastructure/http"; import { HandleIntegrationErrors } from "../../base/errors/decorator"; import { integrationOFetchHttpErrorHandler } from "../../base/errors/http"; @@ -46,6 +46,7 @@ export class TransmissionIntegration extends Integration implements IDownloadCli name: torrent.name, size: torrent.totalSize, sent: torrent.uploadedEver, + received: torrent.downloadedEver, downSpeed: torrent.percentDone !== 1 ? torrent.rateDownload : undefined, upSpeed: torrent.rateUpload, time: diff --git a/packages/integrations/src/emby/emby-integration.ts b/packages/integrations/src/emby/emby-integration.ts index ccfc43222..132a0ac8b 100644 --- a/packages/integrations/src/emby/emby-integration.ts +++ b/packages/integrations/src/emby/emby-integration.ts @@ -1,8 +1,8 @@ import { BaseItemKind } from "@jellyfin/sdk/lib/generated-client/models"; import { z } from "zod/v4"; -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; import { ResponseError } from "@homarr/common/server"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; import type { IntegrationTestingInput } from "../base/integration"; import { Integration } from "../base/integration"; diff --git a/packages/integrations/src/github-container-registry/github-container-registry-integration.ts b/packages/integrations/src/github-container-registry/github-container-registry-integration.ts index 00a7b27c4..1400551a8 100644 --- a/packages/integrations/src/github-container-registry/github-container-registry-integration.ts +++ b/packages/integrations/src/github-container-registry/github-container-registry-integration.ts @@ -2,8 +2,8 @@ import { createAppAuth } from "@octokit/auth-app"; import { Octokit, RequestError } from "octokit"; import type { fetch } from "undici"; -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; -import { logger } from "@homarr/log"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { HandleIntegrationErrors } from "../base/errors/decorator"; import { integrationOctokitHttpErrorHandler } from "../base/errors/http"; @@ -18,7 +18,7 @@ import type { ReleaseResponse, } from "../interfaces/releases-providers/releases-providers-types"; -const localLogger = logger.child({ module: "GitHubContainerRegistryIntegration" }); +const logger = createLogger({ module: "githubContainerRegistryIntegration" }); @HandleIntegrationErrors([integrationOctokitHttpErrorHandler]) export class GitHubContainerRegistryIntegration extends Integration implements ReleasesProviderIntegration { @@ -45,10 +45,7 @@ export class GitHubContainerRegistryIntegration extends Integration implements R private parseIdentifier(identifier: string) { const [owner, name] = identifier.split("/"); if (!owner || !name) { - localLogger.warn( - `Invalid identifier format. Expected 'owner/name', for ${identifier} with GitHub Container Registry integration`, - { identifier }, - ); + logger.warn("Invalid identifier format. Expected 'owner/name', for identifier", { identifier }); return null; } return { owner, name }; @@ -91,7 +88,7 @@ export class GitHubContainerRegistryIntegration extends Integration implements R return { success: true, data: { ...details, ...latestRelease } }; } catch (error) { const errorMessage = error instanceof RequestError ? error.message : String(error); - localLogger.warn(`Failed to get releases for ${owner}\\${name} with GitHub Container Registry integration`, { + logger.warn("Failed to get releases", { owner, name, error: errorMessage, @@ -123,7 +120,7 @@ export class GitHubContainerRegistryIntegration extends Integration implements R forksCount: response.data.repository?.forks_count, }; } catch (error) { - localLogger.warn(`Failed to get details for ${owner}\\${name} with GitHub Container Registry integration`, { + logger.warn("Failed to get details", { owner, name, error: error instanceof RequestError ? error.message : String(error), diff --git a/packages/integrations/src/github/github-integration.ts b/packages/integrations/src/github/github-integration.ts index 65e103c38..01548ecc6 100644 --- a/packages/integrations/src/github/github-integration.ts +++ b/packages/integrations/src/github/github-integration.ts @@ -2,8 +2,8 @@ import { createAppAuth } from "@octokit/auth-app"; import { Octokit, RequestError as OctokitRequestError } from "octokit"; import type { fetch } from "undici"; -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; -import { logger } from "@homarr/log"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { HandleIntegrationErrors } from "../base/errors/decorator"; import { integrationOctokitHttpErrorHandler } from "../base/errors/http"; @@ -18,7 +18,7 @@ import type { ReleaseResponse, } from "../interfaces/releases-providers/releases-providers-types"; -const localLogger = logger.child({ module: "GithubIntegration" }); +const logger = createLogger({ module: "githubIntegration" }); @HandleIntegrationErrors([integrationOctokitHttpErrorHandler]) export class GithubIntegration extends Integration implements ReleasesProviderIntegration { @@ -45,7 +45,7 @@ export class GithubIntegration extends Integration implements ReleasesProviderIn private parseIdentifier(identifier: string) { const [owner, name] = identifier.split("/"); if (!owner || !name) { - localLogger.warn(`Invalid identifier format. Expected 'owner/name', for ${identifier} with Github integration`, { + logger.warn("Invalid identifier format. Expected 'owner/name' for identifier", { identifier, }); return null; @@ -64,7 +64,7 @@ export class GithubIntegration extends Integration implements ReleasesProviderIn const releasesResponse = await api.rest.repos.listReleases({ owner, repo: name }); if (releasesResponse.data.length === 0) { - localLogger.warn(`No releases found, for ${owner}/${name} with Github integration`, { + logger.warn("No releases found", { identifier: `${owner}/${name}`, }); return { success: false, error: { code: "noMatchingVersion" } }; @@ -91,7 +91,7 @@ export class GithubIntegration extends Integration implements ReleasesProviderIn return { success: true, data: { ...details, ...latestRelease } }; } catch (error) { const errorMessage = error instanceof OctokitRequestError ? error.message : String(error); - localLogger.warn(`Failed to get releases for ${owner}\\${name} with Github integration`, { + logger.warn("Failed to get releases", { owner, name, error: errorMessage, @@ -122,7 +122,7 @@ export class GithubIntegration extends Integration implements ReleasesProviderIn forksCount: response.data.forks_count, }; } catch (error) { - localLogger.warn(`Failed to get details for ${owner}\\${name} with Github integration`, { + logger.warn("Failed to get details", { owner, name, error: error instanceof OctokitRequestError ? error.message : String(error), diff --git a/packages/integrations/src/gitlab/gitlab-integration.ts b/packages/integrations/src/gitlab/gitlab-integration.ts index c6bad7a42..fd1ebc926 100644 --- a/packages/integrations/src/gitlab/gitlab-integration.ts +++ b/packages/integrations/src/gitlab/gitlab-integration.ts @@ -3,8 +3,8 @@ import { createRequesterFn, defaultOptionsHandler } from "@gitbeaker/requester-u import type { FormattedResponse, RequestOptions, ResourceOptions } from "@gitbeaker/requester-utils"; import { Gitlab } from "@gitbeaker/rest"; -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; -import { logger } from "@homarr/log"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import type { IntegrationTestingInput } from "../base/integration"; import { Integration } from "../base/integration"; @@ -18,7 +18,7 @@ import type { ReleaseResponse, } from "../interfaces/releases-providers/releases-providers-types"; -const localLogger = logger.child({ module: "GitlabIntegration" }); +const logger = createLogger({ module: "gitlabIntegration" }); export class GitlabIntegration extends Integration implements ReleasesProviderIntegration { protected async testingAsync(input: IntegrationTestingInput): Promise { @@ -48,7 +48,7 @@ export class GitlabIntegration extends Integration implements ReleasesProviderIn }); if (releasesResponse instanceof Error) { - localLogger.warn(`Failed to get releases for ${identifier} with Gitlab integration`, { + logger.warn("No releases found", { identifier, error: releasesResponse.message, }); @@ -78,7 +78,7 @@ export class GitlabIntegration extends Integration implements ReleasesProviderIn return { success: true, data: { ...details, ...latestRelease } }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); - localLogger.warn(`Failed to get releases for ${identifier} with Gitlab integration`, { + logger.warn("Failed to get releases", { identifier, error: errorMessage, }); @@ -91,7 +91,7 @@ export class GitlabIntegration extends Integration implements ReleasesProviderIn const response = await api.Projects.show(identifier); if (response instanceof Error) { - localLogger.warn(`Failed to get details for ${identifier} with Gitlab integration`, { + logger.warn("Failed to get details", { identifier, error: response.message, }); @@ -100,7 +100,7 @@ export class GitlabIntegration extends Integration implements ReleasesProviderIn } if (!response.web_url) { - localLogger.warn(`No web URL found for ${identifier} with Gitlab integration`, { + logger.warn("No web URL found", { identifier, }); return undefined; @@ -117,7 +117,7 @@ export class GitlabIntegration extends Integration implements ReleasesProviderIn forksCount: response.forks_count, }; } catch (error) { - localLogger.warn(`Failed to get details for ${identifier} with Gitlab integration`, { + logger.warn("Failed to get details", { identifier, error: error instanceof Error ? error.message : String(error), }); diff --git a/packages/integrations/src/homeassistant/homeassistant-integration.ts b/packages/integrations/src/homeassistant/homeassistant-integration.ts index c6a0e1191..6da82df46 100644 --- a/packages/integrations/src/homeassistant/homeassistant-integration.ts +++ b/packages/integrations/src/homeassistant/homeassistant-integration.ts @@ -1,8 +1,9 @@ import z from "zod"; -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; import { ResponseError } from "@homarr/common/server"; -import { logger } from "@homarr/log"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; +import { createLogger } from "@homarr/core/infrastructure/logs"; +import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; import type { IntegrationTestingInput } from "../base/integration"; import { Integration } from "../base/integration"; @@ -13,13 +14,15 @@ import type { ISmartHomeIntegration } from "../interfaces/smart-home/smart-home- import type { CalendarEvent } from "../types"; import { calendarEventSchema, calendarsSchema, entityStateSchema } from "./homeassistant-types"; +const logger = createLogger({ module: "homeAssistantIntegration" }); + export class HomeAssistantIntegration extends Integration implements ISmartHomeIntegration, ICalendarIntegration { public async getEntityStateAsync(entityId: string) { try { const response = await this.getAsync(`/api/states/${entityId}`); const body = await response.json(); if (!response.ok) { - logger.warn(`Response did not indicate success`); + logger.warn("Response did not indicate success"); return { success: false as const, error: "Response did not indicate success", @@ -27,7 +30,7 @@ export class HomeAssistantIntegration extends Integration implements ISmartHomeI } return entityStateSchema.safeParseAsync(body); } catch (err) { - logger.error(`Failed to fetch from ${this.url("/")}: ${err as string}`); + logger.error(new ErrorWithMetadata("Failed to fetch entity state", { entityId }, { cause: err })); return { success: false as const, error: err, @@ -43,7 +46,7 @@ export class HomeAssistantIntegration extends Integration implements ISmartHomeI return response.ok; } catch (err) { - logger.error(`Failed to fetch from '${this.url("/")}': ${err as string}`); + logger.error(new ErrorWithMetadata("Failed to trigger automation", { entityId }, { cause: err })); return false; } } @@ -62,7 +65,7 @@ export class HomeAssistantIntegration extends Integration implements ISmartHomeI return response.ok; } catch (err) { - logger.error(`Failed to fetch from '${this.url("/")}': ${err as string}`); + logger.error(new ErrorWithMetadata("Failed to toggle entity", { entityId }, { cause: err })); return false; } } diff --git a/packages/integrations/src/ical/ical-integration.ts b/packages/integrations/src/ical/ical-integration.ts index 8280d4303..0e725bccb 100644 --- a/packages/integrations/src/ical/ical-integration.ts +++ b/packages/integrations/src/ical/ical-integration.ts @@ -1,6 +1,6 @@ import ICAL from "ical.js"; -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; import type { IntegrationTestingInput } from "../base/integration"; import { Integration } from "../base/integration"; diff --git a/packages/integrations/src/interfaces/downloads/download-client-items.ts b/packages/integrations/src/interfaces/downloads/download-client-items.ts index 2fed74538..a700cd5d4 100644 --- a/packages/integrations/src/interfaces/downloads/download-client-items.ts +++ b/packages/integrations/src/interfaces/downloads/download-client-items.ts @@ -26,6 +26,8 @@ export const downloadClientItemSchema = z.object({ size: z.number(), /** Total uploaded in Bytes, only required for Torrent items */ sent: z.number().optional(), + /** Total downloaded in Bytes, only required for Torrent items */ + received: z.number().optional(), /** Download speed in Bytes/s, only required if not complete * (Says 0 only if it should be downloading but isn't) */ downSpeed: z.number().optional(), diff --git a/packages/integrations/src/jellyfin/jellyfin-integration.ts b/packages/integrations/src/jellyfin/jellyfin-integration.ts index 34fee79b6..b2179987c 100644 --- a/packages/integrations/src/jellyfin/jellyfin-integration.ts +++ b/packages/integrations/src/jellyfin/jellyfin-integration.ts @@ -6,7 +6,7 @@ import { getUserApi } from "@jellyfin/sdk/lib/utils/api/user-api"; import { getUserLibraryApi } from "@jellyfin/sdk/lib/utils/api/user-library-api"; import type { AxiosInstance } from "axios"; -import { createAxiosCertificateInstanceAsync } from "@homarr/certificates/server"; +import { createAxiosCertificateInstanceAsync } from "@homarr/core/infrastructure/http"; import { HandleIntegrationErrors } from "../base/errors/decorator"; import { integrationAxiosHttpErrorHandler } from "../base/errors/http"; diff --git a/packages/integrations/src/linuxserverio/linuxserverio-integration.ts b/packages/integrations/src/linuxserverio/linuxserverio-integration.ts index 8b5d1a69b..3178f2c4b 100644 --- a/packages/integrations/src/linuxserverio/linuxserverio-integration.ts +++ b/packages/integrations/src/linuxserverio/linuxserverio-integration.ts @@ -1,5 +1,5 @@ -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; -import { logger } from "@homarr/log"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import type { IntegrationTestingInput } from "../base/integration"; import { Integration } from "../base/integration"; @@ -9,7 +9,7 @@ import type { ReleasesProviderIntegration } from "../interfaces/releases-provide import type { ReleaseResponse } from "../interfaces/releases-providers/releases-providers-types"; import { releasesResponseSchema } from "./linuxserverio-schemas"; -const localLogger = logger.child({ module: "LinuxServerIOsIntegration" }); +const logger = createLogger({ module: "linuxServerIOIntegration" }); export class LinuxServerIOIntegration extends Integration implements ReleasesProviderIntegration { protected async testingAsync(input: IntegrationTestingInput): Promise { @@ -27,10 +27,7 @@ export class LinuxServerIOIntegration extends Integration implements ReleasesPro private parseIdentifier(identifier: string) { const [owner, name] = identifier.split("/"); if (!owner || !name) { - localLogger.warn( - `Invalid identifier format. Expected 'owner/name', for ${identifier} with LinuxServerIO integration`, - { identifier }, - ); + logger.warn("Invalid identifier format. Expected 'owner/name' for identifier", { identifier }); return null; } return { owner, name }; @@ -53,7 +50,7 @@ export class LinuxServerIOIntegration extends Integration implements ReleasesPro const release = data.data.repositories.linuxserver.find((repo) => repo.name === name); if (!release) { - localLogger.warn(`Repository ${name} not found on provider, with LinuxServerIO integration`, { + logger.warn("Repository not found on provider", { name, }); return { success: false, error: { code: "noMatchingVersion" } }; diff --git a/packages/integrations/src/media-organizer/lidarr/lidarr-integration.ts b/packages/integrations/src/media-organizer/lidarr/lidarr-integration.ts index f047048b1..0bac3ec30 100644 --- a/packages/integrations/src/media-organizer/lidarr/lidarr-integration.ts +++ b/packages/integrations/src/media-organizer/lidarr/lidarr-integration.ts @@ -1,7 +1,7 @@ import { z } from "zod/v4"; -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; -import { logger } from "@homarr/log"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { Integration } from "../../base/integration"; import type { IntegrationTestingInput } from "../../base/integration"; @@ -11,6 +11,8 @@ import type { ICalendarIntegration } from "../../interfaces/calendar/calendar-in import type { CalendarEvent, CalendarLink } from "../../interfaces/calendar/calendar-types"; import { mediaOrganizerPriorities } from "../media-organizer"; +const logger = createLogger({ module: "lidarrIntegration" }); + export class LidarrIntegration extends Integration implements ICalendarIntegration { protected async testingAsync(input: IntegrationTestingInput): Promise { const response = await input.fetchAsync(this.url("/api"), { diff --git a/packages/integrations/src/media-organizer/radarr/radarr-integration.ts b/packages/integrations/src/media-organizer/radarr/radarr-integration.ts index ce89df608..b557f7b77 100644 --- a/packages/integrations/src/media-organizer/radarr/radarr-integration.ts +++ b/packages/integrations/src/media-organizer/radarr/radarr-integration.ts @@ -1,7 +1,7 @@ import { z } from "zod/v4"; -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; -import { logger } from "@homarr/log"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import type { IntegrationTestingInput } from "../../base/integration"; import { Integration } from "../../base/integration"; @@ -12,6 +12,8 @@ import type { CalendarEvent, CalendarLink } from "../../interfaces/calendar/cale import { radarrReleaseTypes } from "../../interfaces/calendar/calendar-types"; import { mediaOrganizerPriorities } from "../media-organizer"; +const logger = createLogger({ module: "radarrIntegration" }); + export class RadarrIntegration extends Integration implements ICalendarIntegration { /** * Gets the events in the Radarr calendar between two dates. diff --git a/packages/integrations/src/media-organizer/readarr/readarr-integration.ts b/packages/integrations/src/media-organizer/readarr/readarr-integration.ts index f7b460d9f..aa036626c 100644 --- a/packages/integrations/src/media-organizer/readarr/readarr-integration.ts +++ b/packages/integrations/src/media-organizer/readarr/readarr-integration.ts @@ -1,7 +1,7 @@ import { z } from "zod/v4"; -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; -import { logger } from "@homarr/log"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { Integration } from "../../base/integration"; import type { IntegrationTestingInput } from "../../base/integration"; @@ -11,6 +11,8 @@ import type { ICalendarIntegration } from "../../interfaces/calendar/calendar-in import type { CalendarEvent, CalendarLink } from "../../interfaces/calendar/calendar-types"; import { mediaOrganizerPriorities } from "../media-organizer"; +const logger = createLogger({ module: "readarrIntegration" }); + export class ReadarrIntegration extends Integration implements ICalendarIntegration { protected async testingAsync(input: IntegrationTestingInput): Promise { const response = await input.fetchAsync(this.url("/api"), { diff --git a/packages/integrations/src/media-organizer/sonarr/sonarr-integration.ts b/packages/integrations/src/media-organizer/sonarr/sonarr-integration.ts index 71a739a37..17826e2de 100644 --- a/packages/integrations/src/media-organizer/sonarr/sonarr-integration.ts +++ b/packages/integrations/src/media-organizer/sonarr/sonarr-integration.ts @@ -1,7 +1,7 @@ import { z } from "zod/v4"; -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; -import { logger } from "@homarr/log"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { Integration } from "../../base/integration"; import type { IntegrationTestingInput } from "../../base/integration"; @@ -11,6 +11,8 @@ import type { ICalendarIntegration } from "../../interfaces/calendar/calendar-in import type { CalendarEvent, CalendarLink } from "../../interfaces/calendar/calendar-types"; import { mediaOrganizerPriorities } from "../media-organizer"; +const logger = createLogger({ module: "sonarrIntegration" }); + export class SonarrIntegration extends Integration implements ICalendarIntegration { /** * Gets the events in the Sonarr calendar between two dates. diff --git a/packages/integrations/src/media-transcoding/tdarr-integration.ts b/packages/integrations/src/media-transcoding/tdarr-integration.ts index fc3208f5f..5a16d9ff3 100644 --- a/packages/integrations/src/media-transcoding/tdarr-integration.ts +++ b/packages/integrations/src/media-transcoding/tdarr-integration.ts @@ -1,4 +1,4 @@ -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; import type { IntegrationTestingInput } from "../base/integration"; import { Integration } from "../base/integration"; diff --git a/packages/integrations/src/media-transcoding/tdarr-validation-schemas.ts b/packages/integrations/src/media-transcoding/tdarr-validation-schemas.ts index 4f43e60de..8fd1d0f5d 100644 --- a/packages/integrations/src/media-transcoding/tdarr-validation-schemas.ts +++ b/packages/integrations/src/media-transcoding/tdarr-validation-schemas.ts @@ -7,52 +7,73 @@ export const getStatisticsSchema = z.object({ sizeDiff: z.number(), totalHealthCheckCount: z.number(), status: z.object({ - transcode: z.array( - z.object({ - name: z.string(), - value: z.number(), - }), - ), - healthcheck: z.array( - z.object({ - name: z.string(), - value: z.number(), - }), - ), + transcode: z + .array( + z.object({ + name: z.string(), + value: z.number(), + }), + ) + .optional() + .transform((arr) => arr ?? []), + healthcheck: z + .array( + z.object({ + name: z.string(), + value: z.number(), + }), + ) + .optional() + .transform((arr) => arr ?? []), }), video: z.object({ - codecs: z.array( - z.object({ - name: z.string(), - value: z.number(), - }), - ), - containers: z.array( - z.object({ - name: z.string(), - value: z.number(), - }), - ), - resolutions: z.array( - z.object({ - name: z.string(), - value: z.number(), - }), - ), + codecs: z + .array( + z.object({ + name: z.string(), + value: z.number(), + }), + ) + .optional() + .transform((arr) => arr ?? []), + containers: z + .array( + z.object({ + name: z.string(), + value: z.number(), + }), + ) + .optional() + .transform((arr) => arr ?? []), + resolutions: z + .array( + z.object({ + name: z.string(), + value: z.number(), + }), + ) + .optional() + .transform((arr) => arr ?? []), }), audio: z.object({ - codecs: z.array( - z.object({ - name: z.string(), - value: z.number(), - }), - ), - containers: z.array( - z.object({ - name: z.string(), - value: z.number(), - }), - ), + codecs: z + .array( + z.object({ + name: z.string(), + value: z.number(), + }), + ) + .optional() + .transform((arr) => arr ?? []), + containers: z + .array( + z.object({ + name: z.string(), + value: z.number(), + }), + ) + .optional() + .transform((arr) => arr ?? []), }), }), }); diff --git a/packages/integrations/src/nextcloud/nextcloud.integration.ts b/packages/integrations/src/nextcloud/nextcloud.integration.ts index f767fb671..aa57f480f 100644 --- a/packages/integrations/src/nextcloud/nextcloud.integration.ts +++ b/packages/integrations/src/nextcloud/nextcloud.integration.ts @@ -6,8 +6,8 @@ import type { RequestInit as NodeFetchRequestInit } from "node-fetch"; import * as ical from "node-ical"; import { DAVClient } from "tsdav"; -import { createHttpsAgentAsync } from "@homarr/certificates/server"; -import { logger } from "@homarr/log"; +import { createHttpsAgentAsync } from "@homarr/core/infrastructure/http"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { HandleIntegrationErrors } from "../base/errors/decorator"; import { integrationTsdavHttpErrorHandler } from "../base/errors/http"; @@ -17,6 +17,8 @@ import type { TestingResult } from "../base/test-connection/test-connection-serv import type { ICalendarIntegration } from "../interfaces/calendar/calendar-integration"; import type { CalendarEvent } from "../interfaces/calendar/calendar-types"; +const logger = createLogger({ module: "nextcloudIntegration" }); + dayjs.extend(utc); dayjs.extend(timezone); diff --git a/packages/integrations/src/npm/npm-integration.ts b/packages/integrations/src/npm/npm-integration.ts index 13a307467..b0c8c050b 100644 --- a/packages/integrations/src/npm/npm-integration.ts +++ b/packages/integrations/src/npm/npm-integration.ts @@ -1,4 +1,4 @@ -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; import type { IntegrationTestingInput } from "../base/integration"; import { Integration } from "../base/integration"; diff --git a/packages/integrations/src/ntfy/ntfy-integration.ts b/packages/integrations/src/ntfy/ntfy-integration.ts index 5c6ddb67b..0fa437fec 100644 --- a/packages/integrations/src/ntfy/ntfy-integration.ts +++ b/packages/integrations/src/ntfy/ntfy-integration.ts @@ -1,5 +1,5 @@ -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; import { ResponseError } from "@homarr/common/server"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; import { Integration } from "../base/integration"; import type { IntegrationTestingInput } from "../base/integration"; diff --git a/packages/integrations/src/openmediavault/openmediavault-integration.ts b/packages/integrations/src/openmediavault/openmediavault-integration.ts index 0e5ad1171..128e68a43 100644 --- a/packages/integrations/src/openmediavault/openmediavault-integration.ts +++ b/packages/integrations/src/openmediavault/openmediavault-integration.ts @@ -1,8 +1,8 @@ import type { Headers, HeadersInit, fetch as undiciFetch, Response as UndiciResponse } from "undici"; -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; import { ResponseError } from "@homarr/common/server"; -import { logger } from "@homarr/log"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import type { IntegrationInput, IntegrationTestingInput } from "../base/integration"; import { Integration } from "../base/integration"; @@ -13,7 +13,7 @@ import type { ISystemHealthMonitoringIntegration } from "../interfaces/health-mo import type { SystemHealthMonitoring } from "../types"; import { cpuTempSchema, fileSystemSchema, smartSchema, systemInformationSchema } from "./openmediavault-types"; -const localLogger = logger.child({ module: "OpenMediaVaultIntegration" }); +const logger = createLogger({ module: "openMediaVaultIntegration" }); type SessionStoreValue = | { type: "header"; sessionId: string } @@ -151,13 +151,13 @@ export class OpenMediaVaultIntegration extends Integration implements ISystemHea const storedSession = await this.sessionStore.getAsync(); if (storedSession) { - localLogger.debug("Using stored session for request", { integrationId: this.integration.id }); + logger.debug("Using stored session for request", { integrationId: this.integration.id }); const response = await callback(storedSession); if (response.status !== 401) { return response; } - localLogger.debug("Session expired, getting new session", { integrationId: this.integration.id }); + logger.debug("Session expired, getting new session", { integrationId: this.integration.id }); } const session = await this.getSessionAsync(); diff --git a/packages/integrations/src/opnsense/opnsense-integration.ts b/packages/integrations/src/opnsense/opnsense-integration.ts index 0c1841d55..8ab0e968d 100644 --- a/packages/integrations/src/opnsense/opnsense-integration.ts +++ b/packages/integrations/src/opnsense/opnsense-integration.ts @@ -1,5 +1,5 @@ -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; import { ParseError, ResponseError } from "@homarr/common/server"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; import { createChannelEventHistoryOld } from "../../../redis/src/lib/channel"; import type { IntegrationTestingInput } from "../base/integration"; diff --git a/packages/integrations/src/overseerr/overseerr-integration.ts b/packages/integrations/src/overseerr/overseerr-integration.ts index 0127b2be6..042f2faab 100644 --- a/packages/integrations/src/overseerr/overseerr-integration.ts +++ b/packages/integrations/src/overseerr/overseerr-integration.ts @@ -1,7 +1,8 @@ import { z } from "zod/v4"; -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; -import { logger } from "@homarr/log"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; +import { createLogger } from "@homarr/core/infrastructure/logs"; +import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; import type { IntegrationTestingInput } from "../base/integration"; import { Integration } from "../base/integration"; @@ -21,6 +22,8 @@ import { UpstreamMediaRequestStatus, } from "../interfaces/media-requests/media-request-types"; +const logger = createLogger({ module: "overseerrIntegration" }); + interface OverseerrSearchResult { id: number; name: string; @@ -236,7 +239,7 @@ export class OverseerrIntegration } public async approveRequestAsync(requestId: number): Promise { - logger.info(`Approving media request id='${requestId}' integration='${this.integration.name}'`); + logger.info("Approving media request", { requestId, integration: this.integration.name }); await fetchWithTrustedCertificatesAsync(this.url(`/api/v1/request/${requestId}/approve`), { method: "POST", headers: { @@ -245,16 +248,22 @@ export class OverseerrIntegration }).then((response) => { if (!response.ok) { logger.error( - `Failed to approve media request id='${requestId}' integration='${this.integration.name}' reason='${response.status} ${response.statusText}' url='${response.url}'`, + new ErrorWithMetadata("Failed to approve media request", { + requestId, + integration: this.integration.name, + reason: `${response.status} ${response.statusText}`, + url: response.url, + }), ); } - logger.info(`Successfully approved media request id='${requestId}' integration='${this.integration.name}'`); + logger.info("Successfully approved media request", { requestId, integration: this.integration.name }); }); } public async declineRequestAsync(requestId: number): Promise { - logger.info(`Declining media request id='${requestId}' integration='${this.integration.name}'`); + logger.info("Declining media request", { requestId, integration: this.integration.name }); + await fetchWithTrustedCertificatesAsync(this.url(`/api/v1/request/${requestId}/decline`), { method: "POST", headers: { @@ -263,11 +272,16 @@ export class OverseerrIntegration }).then((response) => { if (!response.ok) { logger.error( - `Failed to decline media request id='${requestId}' integration='${this.integration.name}' reason='${response.status} ${response.statusText}' url='${response.url}'`, + new ErrorWithMetadata("Failed to decline media request", { + requestId, + integration: this.integration.name, + reason: `${response.status} ${response.statusText}`, + url: response.url, + }), ); } - logger.info(`Successfully declined media request id='${requestId}' integration='${this.integration.name}'`); + logger.info("Successfully declined media request", { requestId, integration: this.integration.name }); }); } diff --git a/packages/integrations/src/pi-hole/pi-hole-integration-factory.ts b/packages/integrations/src/pi-hole/pi-hole-integration-factory.ts index 0d542b540..346b40642 100644 --- a/packages/integrations/src/pi-hole/pi-hole-integration-factory.ts +++ b/packages/integrations/src/pi-hole/pi-hole-integration-factory.ts @@ -1,5 +1,5 @@ -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; import { removeTrailingSlash } from "@homarr/common"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; import type { IntegrationInput } from "../base/integration"; import { PiHoleIntegrationV5 } from "./v5/pi-hole-integration-v5"; diff --git a/packages/integrations/src/pi-hole/v5/pi-hole-integration-v5.ts b/packages/integrations/src/pi-hole/v5/pi-hole-integration-v5.ts index 3ffc2e248..f0257615b 100644 --- a/packages/integrations/src/pi-hole/v5/pi-hole-integration-v5.ts +++ b/packages/integrations/src/pi-hole/v5/pi-hole-integration-v5.ts @@ -1,5 +1,5 @@ -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; import { ResponseError } from "@homarr/common/server"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; import type { IntegrationTestingInput } from "../../base/integration"; import { Integration } from "../../base/integration"; diff --git a/packages/integrations/src/pi-hole/v6/pi-hole-integration-v6.ts b/packages/integrations/src/pi-hole/v6/pi-hole-integration-v6.ts index cf9b63599..fd54c0915 100644 --- a/packages/integrations/src/pi-hole/v6/pi-hole-integration-v6.ts +++ b/packages/integrations/src/pi-hole/v6/pi-hole-integration-v6.ts @@ -1,9 +1,9 @@ import type { fetch as undiciFetch, Response as UndiciResponse } from "undici"; import type { z } from "zod/v4"; -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; import { ResponseError } from "@homarr/common/server"; -import { logger } from "@homarr/log"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import type { IntegrationInput, IntegrationTestingInput } from "../../base/integration"; import { Integration } from "../../base/integration"; @@ -14,7 +14,7 @@ import type { DnsHoleSummaryIntegration } from "../../interfaces/dns-hole-summar import type { DnsHoleSummary } from "../../types"; import { dnsBlockingGetSchema, sessionResponseSchema, statsSummaryGetSchema } from "./pi-hole-schemas-v6"; -const localLogger = logger.child({ module: "PiHoleIntegrationV6" }); +const logger = createLogger({ module: "piHoleIntegration", version: "v6" }); export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIntegration { private readonly sessionStore: SessionStore<{ sid: string | null }>; @@ -126,13 +126,13 @@ export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIn const storedSession = await this.sessionStore.getAsync(); if (storedSession) { - localLogger.debug("Using stored session for request", { integrationId: this.integration.id }); + logger.debug("Using stored session for request", { integrationId: this.integration.id }); const response = await callback(storedSession.sid); if (response.status !== 401) { return response; } - localLogger.debug("Session expired, getting new session", { integrationId: this.integration.id }); + logger.debug("Session expired, getting new session", { integrationId: this.integration.id }); } const sessionId = await this.getSessionAsync(); @@ -171,7 +171,7 @@ export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIn ); } - localLogger.info("Received session id successfully", { integrationId: this.integration.id }); + logger.info("Received session id successfully", { integrationId: this.integration.id }); return result.session.sid; } @@ -185,7 +185,7 @@ export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIn fetchAsync: typeof undiciFetch = fetchWithTrustedCertificatesAsync, ) { if (!sessionId) { - localLogger.debug("No session id to clear"); + logger.debug("No session id to clear"); return; } @@ -197,7 +197,7 @@ export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIn }); if (!response.ok) { - localLogger.warn("Failed to clear session", { statusCode: response.status, content: await response.text() }); + logger.warn("Failed to clear session", { statusCode: response.status, content: await response.text() }); } logger.debug("Cleared session successfully"); diff --git a/packages/integrations/src/plex/plex-integration.ts b/packages/integrations/src/plex/plex-integration.ts index ee5b9a6da..9c2cb8a4f 100644 --- a/packages/integrations/src/plex/plex-integration.ts +++ b/packages/integrations/src/plex/plex-integration.ts @@ -1,10 +1,10 @@ import { parseStringPromise } from "xml2js"; import { z } from "zod/v4"; -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; import { ParseError } from "@homarr/common/server"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { ImageProxy } from "@homarr/image-proxy"; -import { logger } from "@homarr/log"; import type { IntegrationTestingInput } from "../base/integration"; import { Integration } from "../base/integration"; @@ -15,6 +15,8 @@ import type { CurrentSessionsInput, StreamSession } from "../interfaces/media-se import type { IMediaReleasesIntegration, MediaRelease } from "../types"; import type { PlexResponse } from "./interface"; +const logger = createLogger({ module: "plexIntegration" }); + export class PlexIntegration extends Integration implements IMediaServerIntegration, IMediaReleasesIntegration { public async getCurrentSessionsAsync(_options: CurrentSessionsInput): Promise { const token = super.getSecretValue("apiKey"); diff --git a/packages/integrations/src/prowlarr/prowlarr-integration.ts b/packages/integrations/src/prowlarr/prowlarr-integration.ts index 22b3bbab2..db6e35f06 100644 --- a/packages/integrations/src/prowlarr/prowlarr-integration.ts +++ b/packages/integrations/src/prowlarr/prowlarr-integration.ts @@ -1,6 +1,6 @@ import { z } from "zod/v4"; -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; import type { IntegrationTestingInput } from "../base/integration"; import { Integration } from "../base/integration"; diff --git a/packages/integrations/src/proxmox/proxmox-integration.ts b/packages/integrations/src/proxmox/proxmox-integration.ts index 6ae65feab..433162186 100644 --- a/packages/integrations/src/proxmox/proxmox-integration.ts +++ b/packages/integrations/src/proxmox/proxmox-integration.ts @@ -1,8 +1,8 @@ import type { Proxmox } from "proxmox-api"; import proxmoxApi from "proxmox-api"; -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; -import { logger } from "@homarr/log"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { HandleIntegrationErrors } from "../base/errors/decorator"; import type { IntegrationTestingInput } from "../base/integration"; @@ -19,6 +19,8 @@ import type { StorageResource, } from "./proxmox-types"; +const logger = createLogger({ module: "proxmoxIntegration" }); + @HandleIntegrationErrors([new ProxmoxApiErrorHandler()]) export class ProxmoxIntegration extends Integration implements IClusterHealthMonitoringIntegration { protected async testingAsync(input: IntegrationTestingInput): Promise { @@ -31,9 +33,13 @@ export class ProxmoxIntegration extends Integration implements IClusterHealthMon const proxmox = this.getPromoxApi(); const resources = await proxmox.cluster.resources.$get(); - logger.info( - `Found ${resources.length} resources in Proxmox cluster node=${resources.filter((resource) => resource.type === "node").length} lxc=${resources.filter((resource) => resource.type === "lxc").length} qemu=${resources.filter((resource) => resource.type === "qemu").length} storage=${resources.filter((resource) => resource.type === "storage").length}`, - ); + logger.info("Found resources in Proxmox cluster", { + total: resources.length, + node: resources.filter((resource) => resource.type === "node").length, + lxc: resources.filter((resource) => resource.type === "lxc").length, + qemu: resources.filter((resource) => resource.type === "qemu").length, + storage: resources.filter((resource) => resource.type === "storage").length, + }); const mappedResources = resources.map(mapResource).filter((resource) => resource !== null); return { diff --git a/packages/integrations/src/quay/quay-integration.ts b/packages/integrations/src/quay/quay-integration.ts index f27349e6b..21cbf797e 100644 --- a/packages/integrations/src/quay/quay-integration.ts +++ b/packages/integrations/src/quay/quay-integration.ts @@ -1,7 +1,7 @@ import type { RequestInit, Response } from "undici"; -import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; -import { logger } from "@homarr/log"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import type { IntegrationTestingInput } from "../base/integration"; import { Integration } from "../base/integration"; @@ -15,7 +15,7 @@ import type { } from "../interfaces/releases-providers/releases-providers-types"; import { releasesResponseSchema } from "./quay-schemas"; -const localLogger = logger.child({ module: "QuayIntegration" }); +const logger = createLogger({ module: "quayIntegration" }); export class QuayIntegration extends Integration implements ReleasesProviderIntegration { private async withHeadersAsync(callback: (headers: RequestInit["headers"]) => Promise): Promise { @@ -45,7 +45,7 @@ export class QuayIntegration extends Integration implements ReleasesProviderInte private parseIdentifier(identifier: string) { const [owner, name] = identifier.split("/"); if (!owner || !name) { - localLogger.warn(`Invalid identifier format. Expected 'owner/name', for ${identifier} with Quay integration`, { + logger.warn("Invalid identifier format. Expected 'owner/name' for identifier", { identifier, }); return null; diff --git a/packages/integrations/src/truenas/truenas-integration.ts b/packages/integrations/src/truenas/truenas-integration.ts index c31b873ba..206f033a0 100644 --- a/packages/integrations/src/truenas/truenas-integration.ts +++ b/packages/integrations/src/truenas/truenas-integration.ts @@ -3,7 +3,7 @@ import z from "zod"; import { createId } from "@homarr/common"; import { RequestError, ResponseError } from "@homarr/common/server"; -import { logger } from "@homarr/log"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import type { IntegrationTestingInput } from "../base/integration"; import { Integration } from "../base/integration"; @@ -11,7 +11,7 @@ import type { TestingResult } from "../base/test-connection/test-connection-serv import type { ISystemHealthMonitoringIntegration } from "../interfaces/health-monitoring/health-monitoring-integration"; import type { SystemHealthMonitoring } from "../interfaces/health-monitoring/health-monitoring-types"; -const localLogger = logger.child({ module: "TrueNasIntegration" }); +const logger = createLogger({ module: "trueNasIntegration" }); const NETWORK_MULTIPLIER = 100; @@ -45,14 +45,14 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni * @see https://www.truenas.com/docs/api/scale_websocket_api.html */ private async connectWebSocketAsync(): Promise { - localLogger.debug("Connecting to websocket server", { + logger.debug("Connecting to websocket server", { url: this.wsUrl(), }); const webSocket = new WebSocket(this.wsUrl()); return new Promise((resolve, reject) => { webSocket.onopen = () => { - localLogger.debug("Connected to websocket server", { + logger.debug("Connected to websocket server", { url: this.wsUrl(), }); resolve(webSocket); @@ -97,7 +97,7 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni * @see https://www.truenas.com/docs/api/scale_websocket_api.html#websocket_protocol */ private async authenticateWebSocketAsync(webSocket?: WebSocket): Promise { - localLogger.debug("Authenticating with username and password", { + logger.debug("Authenticating with username and password", { url: this.wsUrl(), }); const response = await this.requestAsync( @@ -107,7 +107,7 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni ); const result = await z.boolean().parseAsync(response); if (!result) throw new ResponseError({ status: 401 }); - localLogger.debug("Authenticated successfully with username and password", { + logger.debug("Authenticated successfully with username and password", { url: this.wsUrl(), }); } @@ -117,7 +117,7 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni * @see https://www.truenas.com/docs/api/scale_websocket_api.html#reporting */ private async getReportingAsync(): Promise { - localLogger.debug("Retrieving reporting data", { + logger.debug("Retrieving reporting data", { url: this.wsUrl(), }); @@ -141,7 +141,7 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni ]); const result = await z.array(reportingItemSchema).parseAsync(response); - localLogger.debug("Retrieved reporting data", { + logger.debug("Retrieved reporting data", { url: this.wsUrl(), count: result.length, }); @@ -153,7 +153,7 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni * @see https://www.truenas.com/docs/core/13.0/api/core_websocket_api.html#interface */ private async getNetworkInterfacesAsync(): Promise> { - localLogger.debug("Retrieving available network-interfaces", { + logger.debug("Retrieving available network-interfaces", { url: this.wsUrl(), }); @@ -163,7 +163,7 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni ]); const result = await networkInterfaceSchema.parseAsync(response); - localLogger.debug("Retrieved available network-interfaces", { + logger.debug("Retrieved available network-interfaces", { url: this.wsUrl(), count: result.length, }); @@ -177,7 +177,7 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni private async getReportingNetdataAsync(): Promise> { const networkInterfaces = await this.getNetworkInterfacesAsync(); - localLogger.debug("Retrieving reporting network data", { + logger.debug("Retrieving reporting network data", { url: this.wsUrl(), }); @@ -193,7 +193,7 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni ]); const result = await reportingNetDataSchema.parseAsync(response); - localLogger.debug("Retrieved reporting-network-data", { + logger.debug("Retrieved reporting-network-data", { url: this.wsUrl(), count: result.length, }); @@ -205,14 +205,14 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni * @see https://www.truenas.com/docs/api/scale_websocket_api.html#system */ private async getSystemInformationAsync(): Promise> { - localLogger.debug("Retrieving system-information", { + logger.debug("Retrieving system-information", { url: this.wsUrl(), }); const response = await this.requestAsync("system.info"); const result = await systemInfoSchema.parseAsync(response); - localLogger.debug("Retrieved system-information", { + logger.debug("Retrieved system-information", { url: this.wsUrl(), }); return result; @@ -262,7 +262,7 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni private async requestAsync(method: string, params: unknown[] = [], webSocketOverride?: WebSocket) { let webSocket = webSocketOverride ?? this.webSocket; if (!webSocket || webSocket.readyState !== WebSocket.OPEN) { - localLogger.debug("Connecting to websocket", { + logger.debug("Connecting to websocket", { url: this.wsUrl(), }); // We can only land here with static webSocket @@ -282,7 +282,7 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni clearTimeout(timeoutId); webSocket.removeEventListener("message", handler); - localLogger.debug("Received method response", { + logger.debug("Received method response", { id, method, url: this.wsUrl(), @@ -305,7 +305,7 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni webSocket.addEventListener("message", handler); - localLogger.debug("Sending method request", { + logger.debug("Sending method request", { id, method, url: this.wsUrl(), diff --git a/packages/integrations/src/unifi-controller/unifi-controller-integration.ts b/packages/integrations/src/unifi-controller/unifi-controller-integration.ts index f47e18ff7..bee66c956 100644 --- a/packages/integrations/src/unifi-controller/unifi-controller-integration.ts +++ b/packages/integrations/src/unifi-controller/unifi-controller-integration.ts @@ -2,12 +2,12 @@ import type tls from "node:tls"; import axios from "axios"; import { HttpCookieAgent, HttpsCookieAgent } from "http-cookie-agent/http"; +import { getPortFromUrl } from "@homarr/common"; import { - createCustomCheckServerIdentity, getAllTrustedCertificatesAsync, getTrustedCertificateHostnamesAsync, -} from "@homarr/certificates/server"; -import { getPortFromUrl } from "@homarr/common"; +} from "@homarr/core/infrastructure/certificates"; +import { createCustomCheckServerIdentity } from "@homarr/core/infrastructure/http"; import type { SiteStats } from "@homarr/node-unifi"; import Unifi from "@homarr/node-unifi"; diff --git a/packages/integrations/test/aria2.spec.ts b/packages/integrations/test/aria2.spec.ts index 5018ccfdd..a204d7ccf 100644 --- a/packages/integrations/test/aria2.spec.ts +++ b/packages/integrations/test/aria2.spec.ts @@ -14,6 +14,16 @@ vi.mock("@homarr/db", async (importActual) => { db: createDb(), }; }); +vi.mock("@homarr/core/infrastructure/certificates", async (importActual) => { + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + const actual = await importActual(); + return { + ...actual, + getTrustedCertificateHostnamesAsync: vi.fn().mockImplementation(() => { + return Promise.resolve([]); + }), + }; +}); const API_KEY = "ARIA2_API_KEY"; const IMAGE_NAME = "hurlenko/aria2-ariang:latest"; diff --git a/packages/integrations/test/base.spec.ts b/packages/integrations/test/base.spec.ts index 9001a0410..4fcce3458 100644 --- a/packages/integrations/test/base.spec.ts +++ b/packages/integrations/test/base.spec.ts @@ -16,6 +16,17 @@ vi.mock("@homarr/db", async (importActual) => { }; }); +vi.mock("@homarr/core/infrastructure/certificates", async (importActual) => { + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + const actual = await importActual(); + return { + ...actual, + getTrustedCertificateHostnamesAsync: vi.fn().mockImplementation(() => { + return Promise.resolve([]); + }), + }; +}); + describe("Base integration", () => { test("testConnectionAsync should handle errors", async () => { const responseError = new ResponseError({ status: 500, url: "https://example.com" }); diff --git a/packages/integrations/test/home-assistant.spec.ts b/packages/integrations/test/home-assistant.spec.ts index 127e91556..608af1ce0 100644 --- a/packages/integrations/test/home-assistant.spec.ts +++ b/packages/integrations/test/home-assistant.spec.ts @@ -17,6 +17,17 @@ vi.mock("@homarr/db", async (importActual) => { }; }); +vi.mock("@homarr/core/infrastructure/certificates", async (importActual) => { + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + const actual = await importActual(); + return { + ...actual, + getTrustedCertificateHostnamesAsync: vi.fn().mockImplementation(() => { + return Promise.resolve([]); + }), + }; +}); + const DEFAULT_API_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkNjQwY2VjNDFjOGU0NGM5YmRlNWQ4ZmFjMjUzYWViZiIsImlhdCI6MTcxODQ3MTE1MSwiZXhwIjoyMDMzODMxMTUxfQ.uQCZ5FZTokipa6N27DtFhLHkwYEXU1LZr0fsVTryL2Q"; const IMAGE_NAME = "ghcr.io/home-assistant/home-assistant:stable"; diff --git a/packages/integrations/test/nzbget.spec.ts b/packages/integrations/test/nzbget.spec.ts index e0f77a124..6b485e71d 100644 --- a/packages/integrations/test/nzbget.spec.ts +++ b/packages/integrations/test/nzbget.spec.ts @@ -18,6 +18,17 @@ vi.mock("@homarr/db", async (importActual) => { }; }); +vi.mock("@homarr/core/infrastructure/certificates", async (importActual) => { + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + const actual = await importActual(); + return { + ...actual, + getTrustedCertificateHostnamesAsync: vi.fn().mockImplementation(() => { + return Promise.resolve([]); + }), + }; +}); + const username = "nzbget"; const password = "tegbzn6789"; const IMAGE_NAME = "linuxserver/nzbget:latest"; diff --git a/packages/integrations/test/pi-hole.spec.ts b/packages/integrations/test/pi-hole.spec.ts index 837f7ba08..78c272689 100644 --- a/packages/integrations/test/pi-hole.spec.ts +++ b/packages/integrations/test/pi-hole.spec.ts @@ -17,6 +17,17 @@ vi.mock("@homarr/db", async (importActual) => { }; }); +vi.mock("@homarr/core/infrastructure/certificates", async (importActual) => { + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + const actual = await importActual(); + return { + ...actual, + getTrustedCertificateHostnamesAsync: vi.fn().mockImplementation(() => { + return Promise.resolve([]); + }), + }; +}); + const DEFAULT_PASSWORD = "12341234"; const DEFAULT_API_KEY = "3b1434980677dcf53fa8c4a611db3b1f0f88478790097515c0abb539102778b9"; // Some hash generated from password diff --git a/packages/integrations/test/sabnzbd.spec.ts b/packages/integrations/test/sabnzbd.spec.ts index af8c6dcdb..8603f04a0 100644 --- a/packages/integrations/test/sabnzbd.spec.ts +++ b/packages/integrations/test/sabnzbd.spec.ts @@ -18,6 +18,17 @@ vi.mock("@homarr/db", async (importActual) => { }; }); +vi.mock("@homarr/core/infrastructure/certificates", async (importActual) => { + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + const actual = await importActual(); + return { + ...actual, + getTrustedCertificateHostnamesAsync: vi.fn().mockImplementation(() => { + return Promise.resolve([]); + }), + }; +}); + const DEFAULT_API_KEY = "8r45mfes43s3iw7x3oecto6dl9ilxnf9"; const IMAGE_NAME = "linuxserver/sabnzbd:latest"; diff --git a/packages/log/eslint.config.js b/packages/log/eslint.config.js deleted file mode 100644 index f7a5a7d36..000000000 --- a/packages/log/eslint.config.js +++ /dev/null @@ -1,4 +0,0 @@ -import baseConfig from "@homarr/eslint-config/base"; - -/** @type {import('typescript-eslint').Config} */ -export default [...baseConfig]; diff --git a/packages/log/package.json b/packages/log/package.json deleted file mode 100644 index 98e91a949..000000000 --- a/packages/log/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "@homarr/log", - "version": "0.1.0", - "private": true, - "license": "Apache-2.0", - "type": "module", - "exports": { - ".": "./src/index.ts", - "./constants": "./src/constants.ts", - "./env": "./src/env.ts" - }, - "typesVersions": { - "*": { - "*": [ - "src/*" - ] - } - }, - "scripts": { - "clean": "rm -rf .turbo node_modules", - "format": "prettier --check . --ignore-path ../../.gitignore", - "lint": "eslint", - "typecheck": "tsc --noEmit" - }, - "prettier": "@homarr/prettier-config", - "dependencies": { - "@homarr/core": "workspace:^0.1.0", - "superjson": "2.2.6", - "winston": "3.18.3", - "zod": "^4.1.13" - }, - "devDependencies": { - "@homarr/eslint-config": "workspace:^0.2.0", - "@homarr/prettier-config": "workspace:^0.1.0", - "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.39.1", - "typescript": "^5.9.3" - } -} diff --git a/packages/log/src/env.ts b/packages/log/src/env.ts deleted file mode 100644 index 28d463bd6..000000000 --- a/packages/log/src/env.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { z } from "zod/v4"; - -import { createEnv } from "@homarr/core/infrastructure/env"; - -import { logLevels } from "./constants"; - -export const env = createEnv({ - server: { - LOG_LEVEL: z.enum(logLevels).default("info"), - }, - experimental__runtimeEnv: process.env, -}); diff --git a/packages/log/src/index.ts b/packages/log/src/index.ts deleted file mode 100644 index 4337ffb53..000000000 --- a/packages/log/src/index.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { transport as Transport } from "winston"; -import winston, { format, transports } from "winston"; - -import { env } from "./env"; -import { formatErrorCause, formatErrorStack } from "./error"; -import { formatMetadata } from "./metadata"; -import { RedisTransport } from "./redis-transport"; - -const logMessageFormat = format.printf(({ level, message, timestamp, cause, stack, ...metadata }) => { - if (!cause && !stack) { - return `${timestamp as string} ${level}: ${message as string}`; - } - - const formatedStack = formatErrorStack(stack as string | undefined); - - if (!cause) { - return `${timestamp as string} ${level}: ${message as string} ${formatMetadata(metadata)}\n${formatedStack}`; - } - - return `${timestamp as string} ${level}: ${message as string} ${formatMetadata(metadata)}\n${formatedStack}${formatErrorCause(cause)}`; -}); - -const logTransports: Transport[] = [new transports.Console()]; - -// Only add the Redis transport if we are not in CI -if (!(Boolean(process.env.CI) || Boolean(process.env.DISABLE_REDIS_LOGS))) { - logTransports.push( - new RedisTransport({ - level: "debug", - }), - ); -} - -const logger = winston.createLogger({ - format: format.combine( - format.colorize(), - format.timestamp(), - format.errors({ stack: true, cause: true }), - logMessageFormat, - ), - transports: logTransports, - level: env.LOG_LEVEL, -}); - -export { logger }; diff --git a/packages/log/tsconfig.json b/packages/log/tsconfig.json deleted file mode 100644 index cbe8483d9..000000000 --- a/packages/log/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "@homarr/tsconfig/base.json", - "compilerOptions": { - "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" - }, - "include": ["*.ts", "src"], - "exclude": ["node_modules"] -} diff --git a/packages/modals-collection/package.json b/packages/modals-collection/package.json index 4307b0870..acf903de6 100644 --- a/packages/modals-collection/package.json +++ b/packages/modals-collection/package.json @@ -33,19 +33,19 @@ "@homarr/translation": "workspace:^0.1.0", "@homarr/ui": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0", - "@mantine/core": "^8.3.9", + "@mantine/core": "^8.3.10", "@tabler/icons-react": "^3.35.0", "dayjs": "^1.11.19", - "next": "16.0.7", - "react": "19.2.1", - "react-dom": "19.2.1", + "next": "16.0.10", + "react": "19.2.3", + "react-dom": "19.2.3", "zod": "^4.1.13" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/modals-collection/src/invites/invite-create-modal.tsx b/packages/modals-collection/src/invites/invite-create-modal.tsx index 85d373474..5f4db6a29 100644 --- a/packages/modals-collection/src/invites/invite-create-modal.tsx +++ b/packages/modals-collection/src/invites/invite-create-modal.tsx @@ -13,7 +13,7 @@ import { InviteCopyModal } from "./invite-copy-modal"; dayjs.extend(relativeTime); interface FormType { - expirationDate: Date; + expirationDate: string; } export const InviteCreateModal = createModal(({ actions }) => { @@ -28,18 +28,23 @@ export const InviteCreateModal = createModal(({ actions }) => { const form = useForm({ initialValues: { - expirationDate: dayjs().add(4, "hours").toDate(), + expirationDate: dayjs().add(4, "hours").toDate().toISOString(), }, }); const handleSubmit = (values: FormType) => { - mutate(values, { - onSuccess: (result) => { - void utils.invite.getAll.invalidate(); - actions.closeModal(); - openModal(result); + mutate( + { + expirationDate: new Date(values.expirationDate), }, - }); + { + onSuccess: (result) => { + void utils.invite.getAll.invalidate(); + actions.closeModal(); + openModal(result); + }, + }, + ); }; return ( diff --git a/packages/modals/package.json b/packages/modals/package.json index 57e169ce8..42afcdde8 100644 --- a/packages/modals/package.json +++ b/packages/modals/package.json @@ -24,15 +24,15 @@ "dependencies": { "@homarr/translation": "workspace:^0.1.0", "@homarr/ui": "workspace:^0.1.0", - "@mantine/core": "^8.3.9", - "@mantine/hooks": "^8.3.9", - "react": "19.2.1" + "@mantine/core": "^8.3.10", + "@mantine/hooks": "^8.3.10", + "react": "19.2.3" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/notifications/package.json b/packages/notifications/package.json index fd93c4b8d..368645e8b 100644 --- a/packages/notifications/package.json +++ b/packages/notifications/package.json @@ -24,14 +24,14 @@ "prettier": "@homarr/prettier-config", "dependencies": { "@homarr/ui": "workspace:^0.1.0", - "@mantine/notifications": "^8.3.9", + "@mantine/notifications": "^8.3.10", "@tabler/icons-react": "^3.35.0" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/old-import/package.json b/packages/old-import/package.json index 713fec2aa..5981a251a 100644 --- a/packages/old-import/package.json +++ b/packages/old-import/package.json @@ -27,22 +27,22 @@ "prettier": "@homarr/prettier-config", "dependencies": { "@homarr/common": "workspace:^0.1.0", + "@homarr/core": "workspace:^0.1.0", "@homarr/db": "workspace:^0.1.0", "@homarr/definitions": "workspace:^0.1.0", "@homarr/form": "workspace:^0.1.0", - "@homarr/log": "workspace:^0.1.0", "@homarr/modals": "workspace:^0.1.0", "@homarr/notifications": "workspace:^0.1.0", "@homarr/old-schema": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0", "@homarr/ui": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0", - "@mantine/core": "^8.3.9", - "@mantine/hooks": "^8.3.9", + "@mantine/core": "^8.3.10", + "@mantine/hooks": "^8.3.10", "adm-zip": "0.5.16", - "next": "16.0.7", - "react": "19.2.1", - "react-dom": "19.2.1", + "next": "16.0.10", + "react": "19.2.3", + "react-dom": "19.2.3", "superjson": "2.2.6", "zod": "^4.1.13", "zod-form-data": "^3.0.1" @@ -52,7 +52,7 @@ "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", "@types/adm-zip": "0.5.7", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/old-import/src/analyse/analyse-oldmarr-import.ts b/packages/old-import/src/analyse/analyse-oldmarr-import.ts index c526728af..2a440b979 100644 --- a/packages/old-import/src/analyse/analyse-oldmarr-import.ts +++ b/packages/old-import/src/analyse/analyse-oldmarr-import.ts @@ -1,12 +1,15 @@ import AdmZip from "adm-zip"; import { z } from "zod/v4"; -import { logger } from "@homarr/log"; +import { createLogger } from "@homarr/core/infrastructure/logs"; +import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; import { oldmarrConfigSchema } from "@homarr/old-schema"; import { oldmarrImportUserSchema } from "../user-schema"; import type { analyseOldmarrImportInputSchema } from "./input"; +const logger = createLogger({ module: "analyseOldmarrImport" }); + export const analyseOldmarrImportForRouterAsync = async (input: z.infer) => { const { configs, checksum, users } = await analyseOldmarrImportAsync(input.file); @@ -25,7 +28,13 @@ export const analyseOldmarrImportAsync = async (file: File) => { const configs = configEntries.map((entry) => { const result = oldmarrConfigSchema.safeParse(JSON.parse(entry.getData().toString())); if (!result.success) { - logger.error(`Failed to parse config ${entry.entryName} with error: ${JSON.stringify(result.error)}`); + logger.error( + new ErrorWithMetadata( + "Failed to parse oldmarr config", + { entryName: entry.entryName }, + { cause: result.error }, + ), + ); } return { @@ -57,7 +66,7 @@ const parseUsers = (entry: AdmZip.IZipEntry | undefined) => { const result = z.array(oldmarrImportUserSchema).safeParse(JSON.parse(entry.getData().toString())); if (!result.success) { - logger.error(`Failed to parse users with error: ${JSON.stringify(result.error)}`); + logger.error(new Error("Failed to parse users", { cause: result.error })); } return result.data ?? []; diff --git a/packages/old-import/src/fix-section-issues.ts b/packages/old-import/src/fix-section-issues.ts index b34c24871..0a092deec 100644 --- a/packages/old-import/src/fix-section-issues.ts +++ b/packages/old-import/src/fix-section-issues.ts @@ -1,7 +1,9 @@ import { createId } from "@homarr/common"; -import { logger } from "@homarr/log"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import type { OldmarrConfig } from "@homarr/old-schema"; +const logger = createLogger({ module: "fixSectionIssues" }); + export const fixSectionIssues = (old: OldmarrConfig) => { const wrappers = old.wrappers.sort((wrapperA, wrapperB) => wrapperA.position - wrapperB.position); const categories = old.categories.sort((categoryA, categoryB) => categoryA.position - categoryB.position); @@ -9,9 +11,10 @@ export const fixSectionIssues = (old: OldmarrConfig) => { const neededSectionCount = categories.length * 2 + 1; const hasToMuchEmptyWrappers = wrappers.length > categories.length + 1; - logger.debug( - `Fixing section issues neededSectionCount=${neededSectionCount} hasToMuchEmptyWrappers=${hasToMuchEmptyWrappers}`, - ); + logger.debug("Fixing section issues", { + neededSectionCount, + hasToMuchEmptyWrappers, + }); for (let position = 0; position < neededSectionCount; position++) { const index = Math.floor(position / 2); @@ -38,7 +41,7 @@ export const fixSectionIssues = (old: OldmarrConfig) => { wrappers.splice(categories.length + 1); if (wrapperIdsToMerge.length >= 2) { - logger.debug(`Found wrappers to merge count=${wrapperIdsToMerge.length}`); + logger.debug("Found wrappers to merge", { count: wrapperIdsToMerge.length }); } return { diff --git a/packages/old-import/src/import-sections.ts b/packages/old-import/src/import-sections.ts index f1900790b..78a1b1619 100644 --- a/packages/old-import/src/import-sections.ts +++ b/packages/old-import/src/import-sections.ts @@ -1,18 +1,18 @@ import { createId } from "@homarr/common"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import type { Database } from "@homarr/db"; import { sections } from "@homarr/db/schema"; -import { logger } from "@homarr/log"; import type { OldmarrConfig } from "@homarr/old-schema"; +const logger = createLogger({ module: "importSections" }); + export const insertSectionsAsync = async ( db: Database, categories: OldmarrConfig["categories"], wrappers: OldmarrConfig["wrappers"], boardId: string, ) => { - logger.info( - `Importing old homarr sections boardId=${boardId} categories=${categories.length} wrappers=${wrappers.length}`, - ); + logger.info("Importing old homarr sections", { boardId, categories: categories.length, wrappers: wrappers.length }); const wrapperIds = wrappers.map((section) => section.id); const categoryIds = categories.map((section) => section.id); @@ -45,7 +45,7 @@ export const insertSectionsAsync = async ( await db.insert(sections).values(categoriesToInsert); } - logger.info(`Imported sections count=${wrappersToInsert.length + categoriesToInsert.length}`); + logger.info("Imported sections", { count: wrappersToInsert.length + categoriesToInsert.length }); return idMaps; }; diff --git a/packages/old-import/src/import/collections/board-collection.ts b/packages/old-import/src/import/collections/board-collection.ts index df8d72648..65bfb37f0 100644 --- a/packages/old-import/src/import/collections/board-collection.ts +++ b/packages/old-import/src/import/collections/board-collection.ts @@ -1,6 +1,6 @@ import { createId } from "@homarr/common"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { createDbInsertCollectionForTransaction } from "@homarr/db/collection"; -import { logger } from "@homarr/log"; import type { BoardSize, OldmarrConfig } from "@homarr/old-schema"; import { boardSizes, getBoardSizeName } from "@homarr/old-schema"; @@ -15,6 +15,8 @@ import type { prepareMultipleImports } from "../../prepare/prepare-multiple"; import { prepareSections } from "../../prepare/prepare-sections"; import type { InitialOldmarrImportSettings } from "../../settings"; +const logger = createLogger({ module: "boardCollection" }); + export const createBoardInsertCollection = ( { preparedApps, preparedBoards }: Omit, "preparedIntegrations">, settings: InitialOldmarrImportSettings, @@ -50,12 +52,12 @@ export const createBoardInsertCollection = ( } if (settings.onlyImportApps) { - logger.info( - `Skipping boards and sections import due to onlyImportApps setting appCount=${insertCollection.apps.length}`, - ); + logger.info("Skipping boards and sections import due to onlyImportApps setting", { + appCount: insertCollection.apps.length, + }); return insertCollection; } - logger.debug(`Added apps to board insert collection count=${insertCollection.apps.length}`); + logger.debug("Added apps to board insert collection", { count: insertCollection.apps.length }); preparedBoards.forEach((board) => { if (!hasEnoughItemShapes(board.config)) { @@ -71,10 +73,10 @@ export const createBoardInsertCollection = ( name: board.name, }); - logger.debug(`Fixed issues with sections and item positions fileName=${board.name}`); + logger.debug("Fixed issues with sections and item positions", { fileName: board.name }); const mappedBoard = mapBoard(board); - logger.debug(`Mapped board fileName=${board.name} boardId=${mappedBoard.id}`); + logger.debug("Mapped board", { fileName: board.name, boardId: mappedBoard.id }); insertCollection.boards.push(mappedBoard); const layoutMapping = boardSizes.reduce( @@ -100,7 +102,7 @@ export const createBoardInsertCollection = ( for (const section of preparedSections.values()) { insertCollection.sections.push(section); } - logger.debug(`Added sections to board insert collection count=${insertCollection.sections.length}`); + logger.debug("Added sections to board insert collection", { count: insertCollection.sections.length }); const preparedItems = prepareItems( { @@ -117,12 +119,15 @@ export const createBoardInsertCollection = ( insertCollection.items.push(item); insertCollection.itemLayouts.push(...layouts); }); - logger.debug(`Added items to board insert collection count=${insertCollection.items.length}`); + logger.debug("Added items to board insert collection", { count: insertCollection.items.length }); }); - logger.info( - `Board collection prepared boardCount=${insertCollection.boards.length} sectionCount=${insertCollection.sections.length} itemCount=${insertCollection.items.length} appCount=${insertCollection.apps.length}`, - ); + logger.info("Board collection prepared", { + boardCount: insertCollection.boards.length, + sectionCount: insertCollection.sections.length, + itemCount: insertCollection.items.length, + appCount: insertCollection.apps.length, + }); return insertCollection; }; diff --git a/packages/old-import/src/import/collections/integration-collection.ts b/packages/old-import/src/import/collections/integration-collection.ts index 4a891a808..33b182040 100644 --- a/packages/old-import/src/import/collections/integration-collection.ts +++ b/packages/old-import/src/import/collections/integration-collection.ts @@ -1,10 +1,12 @@ import { encryptSecret } from "@homarr/common/server"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { createDbInsertCollectionForTransaction } from "@homarr/db/collection"; -import { logger } from "@homarr/log"; import { mapAndDecryptIntegrations } from "../../mappers/map-integration"; import type { PreparedIntegration } from "../../prepare/prepare-integrations"; +const logger = createLogger({ module: "integrationCollection" }); + export const createIntegrationInsertCollection = ( preparedIntegrations: PreparedIntegration[], encryptionToken: string | null | undefined, @@ -15,7 +17,7 @@ export const createIntegrationInsertCollection = ( return insertCollection; } - logger.info(`Preparing integrations for insert collection count=${preparedIntegrations.length}`); + logger.info("Preparing integrations for insert collection", { count: preparedIntegrations.length }); if (encryptionToken === null || encryptionToken === undefined) { logger.debug("Skipping integration decryption due to missing token"); @@ -44,9 +46,10 @@ export const createIntegrationInsertCollection = ( }); }); - logger.info( - `Added integrations and secrets to insert collection integrationCount=${insertCollection.integrations.length} secretCount=${insertCollection.integrationSecrets.length}`, - ); + logger.info("Added integrations and secrets to insert collection", { + integrationCount: insertCollection.integrations.length, + secretCount: insertCollection.integrationSecrets.length, + }); return insertCollection; }; diff --git a/packages/old-import/src/import/collections/user-collection.ts b/packages/old-import/src/import/collections/user-collection.ts index 5f8a54117..3fb9cc14b 100644 --- a/packages/old-import/src/import/collections/user-collection.ts +++ b/packages/old-import/src/import/collections/user-collection.ts @@ -1,11 +1,13 @@ import { createId } from "@homarr/common"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { createDbInsertCollectionForTransaction } from "@homarr/db/collection"; import { credentialsAdminGroup } from "@homarr/definitions"; -import { logger } from "@homarr/log"; import { mapAndDecryptUsers } from "../../mappers/map-user"; import type { OldmarrImportUser } from "../../user-schema"; +const logger = createLogger({ module: "userCollection" }); + export const createUserInsertCollection = ( importUsers: OldmarrImportUser[], encryptionToken: string | null | undefined, @@ -21,7 +23,7 @@ export const createUserInsertCollection = ( return insertCollection; } - logger.info(`Preparing users for insert collection count=${importUsers.length}`); + logger.info("Preparing users for insert collection", { count: importUsers.length }); if (encryptionToken === null || encryptionToken === undefined) { logger.debug("Skipping user decryption due to missing token"); @@ -30,7 +32,7 @@ export const createUserInsertCollection = ( const preparedUsers = mapAndDecryptUsers(importUsers, encryptionToken); preparedUsers.forEach((user) => insertCollection.users.push(user)); - logger.debug(`Added users to insert collection count=${insertCollection.users.length}`); + logger.debug("Added users to insert collection", { count: insertCollection.users.length }); if (!preparedUsers.some((user) => user.isAdmin)) { logger.warn("No admin users found, skipping admin group creation"); @@ -58,9 +60,10 @@ export const createUserInsertCollection = ( }); }); - logger.info( - `Added admin group and permissions to insert collection adminGroupId=${adminGroupId} adminUsersCount=${admins.length}`, - ); + logger.info("Added admin group and permissions to insert collection", { + adminGroupId, + adminUsersCount: admins.length, + }); return insertCollection; }; diff --git a/packages/old-import/src/import/import-initial-oldmarr.ts b/packages/old-import/src/import/import-initial-oldmarr.ts index a42bbba83..f773c1b36 100644 --- a/packages/old-import/src/import/import-initial-oldmarr.ts +++ b/packages/old-import/src/import/import-initial-oldmarr.ts @@ -1,9 +1,9 @@ import type { z } from "zod/v4"; import { Stopwatch } from "@homarr/common"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { handleTransactionsAsync } from "@homarr/db"; import type { Database } from "@homarr/db"; -import { logger } from "@homarr/log"; import { analyseOldmarrImportAsync } from "../analyse/analyse-oldmarr-import"; import { prepareMultipleImports } from "../prepare/prepare-multiple"; @@ -13,6 +13,8 @@ import { createUserInsertCollection } from "./collections/user-collection"; import type { importInitialOldmarrInputSchema } from "./input"; import { ensureValidTokenOrThrow } from "./validate-token"; +const logger = createLogger({ module: "importInitialOldmarr" }); + export const importInitialOldmarrAsync = async ( db: Database, input: z.infer, @@ -52,5 +54,5 @@ export const importInitialOldmarrAsync = async ( }, }); - logger.info(`Import successful (in ${stopwatch.getElapsedInHumanWords()})`); + logger.info("Import successful", { duration: stopwatch.getElapsedInHumanWords() }); }; diff --git a/packages/old-import/src/mappers/map-item.ts b/packages/old-import/src/mappers/map-item.ts index f4c471728..2e339a0b2 100644 --- a/packages/old-import/src/mappers/map-item.ts +++ b/packages/old-import/src/mappers/map-item.ts @@ -1,9 +1,9 @@ import SuperJSON from "superjson"; import { createId } from "@homarr/common"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import type { InferInsertModel } from "@homarr/db"; import type { itemLayouts, items } from "@homarr/db/schema"; -import { logger } from "@homarr/log"; import type { BoardSize, OldmarrApp, OldmarrWidget } from "@homarr/old-schema"; import { boardSizes } from "@homarr/old-schema"; @@ -11,6 +11,8 @@ import type { WidgetComponentProps } from "../../../widgets/src/definition"; import { mapKind } from "../widgets/definitions"; import { mapOptions } from "../widgets/options"; +const logger = createLogger({ module: "mapItem" }); + export const mapApp = ( app: OldmarrApp, appsMap: Map, @@ -22,7 +24,10 @@ export const mapApp = ( const sectionId = sectionMap.get(app.area.properties.id)?.id; if (!sectionId) { - logger.warn(`Failed to find section for app appId='${app.id}' sectionId='${app.area.properties.id}'. Removing app`); + logger.warn("Failed to find section for app. Removing app", { + appId: app.id, + sectionId: app.area.properties.id, + }); return null; } @@ -71,15 +76,19 @@ export const mapWidget = ( const kind = mapKind(widget.type); if (!kind) { - logger.warn(`Failed to map widget type='${widget.type}'. It's no longer supported`); + logger.warn("Failed to map widget type. It's no longer supported", { + widgetId: widget.id, + widgetType: widget.type, + }); return null; } const sectionId = sectionMap.get(widget.area.properties.id)?.id; if (!sectionId) { - logger.warn( - `Failed to find section for widget widgetId='${widget.id}' sectionId='${widget.area.properties.id}'. Removing widget`, - ); + logger.warn("Failed to find section for widget. Removing widget", { + widgetId: widget.id, + sectionId: widget.area.properties.id, + }); return null; } diff --git a/packages/old-import/src/move-widgets-and-apps-merge.ts b/packages/old-import/src/move-widgets-and-apps-merge.ts index 5b77fa925..5cb74a160 100644 --- a/packages/old-import/src/move-widgets-and-apps-merge.ts +++ b/packages/old-import/src/move-widgets-and-apps-merge.ts @@ -1,11 +1,13 @@ import { objectEntries } from "@homarr/common"; -import { logger } from "@homarr/log"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import type { BoardSize, OldmarrApp, OldmarrConfig, OldmarrWidget } from "@homarr/old-schema"; import { boardSizes } from "@homarr/old-schema"; import { mapColumnCount } from "./mappers/map-column-count"; import type { OldmarrImportConfiguration } from "./settings"; +const logger = createLogger({ module: "moveWidgetsAndAppsMerge" }); + export const moveWidgetsAndAppsIfMerge = ( old: OldmarrConfig, wrapperIdsToMerge: string[], @@ -26,7 +28,7 @@ export const moveWidgetsAndAppsIfMerge = ( ]), ); - logger.debug(`Merging wrappers at the end of the board count=${wrapperIdsToMerge.length}`); + logger.debug("Merging wrappers at the end of the board", { count: wrapperIdsToMerge.length }); const offsets = boardSizes.reduce( (previous, screenSize) => { diff --git a/packages/old-import/src/prepare/prepare-items.ts b/packages/old-import/src/prepare/prepare-items.ts index bbb96f171..0f32d6b00 100644 --- a/packages/old-import/src/prepare/prepare-items.ts +++ b/packages/old-import/src/prepare/prepare-items.ts @@ -1,4 +1,4 @@ -import { logger } from "@homarr/log"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import type { BoardSize, OldmarrApp, OldmarrConfig, OldmarrWidget, SizedShape } from "@homarr/old-schema"; import { boardSizes } from "@homarr/old-schema"; @@ -7,6 +7,8 @@ import { generateResponsiveGridFor } from "../../../api/src/router/board/grid-al import { mapColumnCount } from "../mappers/map-column-count"; import { mapApp, mapWidget } from "../mappers/map-item"; +const logger = createLogger({ module: "prepareItems" }); + export const prepareItems = ( { apps, widgets, settings }: Pick, appsMap: Map, @@ -25,15 +27,17 @@ export const prepareItems = ( ); if (incompleteSizes.length > 0) { - logger.warn( - `Found items with incomplete sizes board=${boardId} count=${incompleteSizes.length} sizes=${incompleteSizes.join(", ")}\nHomarr will automatically generate missing sizes`, - ); + logger.warn("Found items with incomplete sizes. Generating missing sizes.", { + boardId, + count: incompleteSizes.length, + sizes: incompleteSizes.join(", "), + }); incompleteSizes.forEach((size) => { const columnCount = mapColumnCount(settings.customization.gridstack, size); const previousSize = !incompleteSizes.includes("lg") ? "lg" : incompleteSizes.includes("sm") ? "md" : "sm"; const previousWidth = mapColumnCount(settings.customization.gridstack, previousSize); - logger.info(`Generating missing size boardId=${boardId} from=${previousSize} to=${size}`); + logger.info("Generating missing size", { boardId, from: previousSize, to: size }); const items = widgets .map((item) => mapItemForGridAlgorithm(item, previousSize)) diff --git a/packages/old-import/src/widgets/options.ts b/packages/old-import/src/widgets/options.ts index 60c5302f6..e3c58768f 100644 --- a/packages/old-import/src/widgets/options.ts +++ b/packages/old-import/src/widgets/options.ts @@ -1,10 +1,12 @@ import { objectEntries } from "@homarr/common"; -import { logger } from "@homarr/log"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import type { WidgetComponentProps } from "../../../widgets/src/definition"; import type { InversedWidgetMapping, OldmarrWidgetDefinitions, WidgetMapping } from "./definitions"; import { mapKind } from "./definitions"; +const logger = createLogger({ module: "mapOptions" }); + // This type enforces, that for all widget mappings there is a corresponding option mapping, // each option of newmarr can be mapped from the value of the oldmarr options type OptionMapping = { @@ -192,7 +194,7 @@ export const mapOptions = ( oldOptions: Extract["options"], appsMap: Map, ) => { - logger.debug(`Mapping old homarr options for widget type=${type} options=${JSON.stringify(oldOptions)}`); + logger.debug("Mapping old homarr options for widget", { type, options: JSON.stringify(oldOptions) }); const kind = mapKind(type); if (!kind) { return null; @@ -202,7 +204,7 @@ export const mapOptions = ( return objectEntries(mapping).reduce( (acc, [key, value]: [string, (oldOptions: Record, appsMap: Map) => unknown]) => { const newValue = value(oldOptions, appsMap); - logger.debug(`Mapping old homarr option kind=${kind} key=${key} newValue=${newValue as string}`); + logger.debug("Mapping old homarr option", { kind, key, newValue }); if (newValue !== undefined) { acc[key] = newValue; } diff --git a/packages/old-schema/package.json b/packages/old-schema/package.json index 5a5ec741f..41e3e8f0e 100644 --- a/packages/old-schema/package.json +++ b/packages/old-schema/package.json @@ -29,7 +29,7 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/ping/package.json b/packages/ping/package.json index 364ca9f86..c73010fdc 100644 --- a/packages/ping/package.json +++ b/packages/ping/package.json @@ -22,15 +22,14 @@ }, "prettier": "@homarr/prettier-config", "dependencies": { - "@homarr/certificates": "workspace:^0.1.0", "@homarr/common": "workspace:^0.1.0", - "@homarr/log": "workspace:^0.1.0" + "@homarr/core": "workspace:^0.1.0" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/ping/src/index.ts b/packages/ping/src/index.ts index 1bf624706..124acb825 100644 --- a/packages/ping/src/index.ts +++ b/packages/ping/src/index.ts @@ -1,35 +1,32 @@ import { fetch } from "undici"; import { extractErrorMessage } from "@homarr/common"; -import { LoggingAgent } from "@homarr/common/server"; -import { logger } from "@homarr/log"; +import { UndiciHttpAgent } from "@homarr/core/infrastructure/http"; +import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout"; +import { createLogger } from "@homarr/core/infrastructure/logs"; +import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; + +const logger = createLogger({ module: "ping" }); export const sendPingRequestAsync = async (url: string) => { try { - const controller = new AbortController(); - - // 10 seconds timeout: - const timeoutId = setTimeout(() => controller.abort(), 10000); const start = performance.now(); - - return await fetch(url, { - dispatcher: new LoggingAgent({ - connect: { - rejectUnauthorized: false, // Ping should always work, even with untrusted certificates - }, - }), - signal: controller.signal, - }) - .finally(() => { - clearTimeout(timeoutId); - }) - .then((response) => { - const end = performance.now(); - const durationMs = end - start; - return { statusCode: response.status, durationMs }; + return await withTimeoutAsync(async (signal) => { + return await fetch(url, { + dispatcher: new UndiciHttpAgent({ + connect: { + rejectUnauthorized: false, // Ping should always work, even with untrusted certificates + }, + }), + signal, }); + }).then((response) => { + const end = performance.now(); + const durationMs = end - start; + return { statusCode: response.status, durationMs }; + }); } catch (error) { - logger.error(new Error(`Failed to send ping request to "${url}"`, { cause: error })); + logger.error(new ErrorWithMetadata("Failed to send ping request", { url }, { cause: error })); return { error: extractErrorMessage(error), }; diff --git a/packages/redis/package.json b/packages/redis/package.json index 97df1742d..ff18cc7a5 100644 --- a/packages/redis/package.json +++ b/packages/redis/package.json @@ -26,7 +26,6 @@ "@homarr/core": "workspace:^", "@homarr/db": "workspace:^", "@homarr/definitions": "workspace:^", - "@homarr/log": "workspace:^", "ioredis": "5.8.2", "superjson": "2.2.6" }, @@ -34,7 +33,7 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/redis/src/index.ts b/packages/redis/src/index.ts index bd44350c2..f8d8fd329 100644 --- a/packages/redis/src/index.ts +++ b/packages/redis/src/index.ts @@ -1,4 +1,4 @@ -import type { LogLevel } from "@homarr/log/constants"; +import type { LogLevel } from "@homarr/core/infrastructure/logs/constants"; import { createListChannel, createQueueChannel, createSubPubChannel } from "./lib/channel"; diff --git a/packages/redis/src/lib/channel-subscription-tracker.ts b/packages/redis/src/lib/channel-subscription-tracker.ts index 8b98b4732..02adc6897 100644 --- a/packages/redis/src/lib/channel-subscription-tracker.ts +++ b/packages/redis/src/lib/channel-subscription-tracker.ts @@ -1,10 +1,12 @@ import { randomUUID } from "crypto"; import type { MaybePromise } from "@homarr/common/types"; -import { logger } from "@homarr/log"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { createRedisConnection } from "./connection"; +const logger = createLogger({ module: "channelSubscriptionTracker" }); + type SubscriptionCallback = (message: string) => MaybePromise; /** @@ -26,7 +28,7 @@ export class ChannelSubscriptionTracker { * @returns a function to unsubscribe from the channel */ public static subscribe(channelName: string, callback: SubscriptionCallback) { - logger.debug(`Adding redis channel callback channel='${channelName}'`); + logger.debug("Adding redis channel callback", { channel: channelName }); // We only want to activate the listener once if (!this.listenerActive) { @@ -39,18 +41,18 @@ export class ChannelSubscriptionTracker { // If there are no subscriptions to the channel, subscribe to it if (channelSubscriptions.size === 0) { - logger.debug(`Subscribing to redis channel channel='${channelName}'`); + logger.debug("Subscribing to redis channel", { channel: channelName }); void this.redis.subscribe(channelName); } - logger.debug(`Adding redis channel callback channel='${channelName}' id='${id}'`); + logger.debug("Adding redis channel callback", { channel: channelName, id }); channelSubscriptions.set(id, callback); this.subscriptions.set(channelName, channelSubscriptions); // Return a function to unsubscribe return () => { - logger.debug(`Removing redis channel callback channel='${channelName}' id='${id}'`); + logger.debug("Removing redis channel callback", { channel: channelName, id }); const channelSubscriptions = this.subscriptions.get(channelName); if (!channelSubscriptions) return; @@ -62,7 +64,7 @@ export class ChannelSubscriptionTracker { return; } - logger.debug(`Unsubscribing from redis channel channel='${channelName}'`); + logger.debug("Unsubscribing from redis channel", { channel: channelName }); void this.redis.unsubscribe(channelName); this.subscriptions.delete(channelName); }; @@ -76,14 +78,14 @@ export class ChannelSubscriptionTracker { this.redis.on("message", (channel, message) => { const channelSubscriptions = this.subscriptions.get(channel); if (!channelSubscriptions) { - logger.warn(`Received message on unknown channel channel='${channel}'`); + logger.warn("Received message on unknown channel", { channel }); return; } for (const [id, callback] of channelSubscriptions.entries()) { // Don't log messages from the logging channel as it would create an infinite loop if (channel !== "pubSub:logging") { - logger.debug(`Calling subscription callback channel='${channel}' id='${id}'`); + logger.debug("Calling subscription callback", { channel, id }); } void callback(message); } diff --git a/packages/redis/src/lib/channel.ts b/packages/redis/src/lib/channel.ts index 074f66414..193f551e3 100644 --- a/packages/redis/src/lib/channel.ts +++ b/packages/redis/src/lib/channel.ts @@ -1,12 +1,14 @@ import superjson from "superjson"; import { createId, hashObjectBase64 } from "@homarr/common"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import type { WidgetKind } from "@homarr/definitions"; -import { logger } from "@homarr/log"; import { ChannelSubscriptionTracker } from "./channel-subscription-tracker"; import { createRedisConnection } from "./connection"; +const logger = createLogger({ module: "redisChannel" }); + const publisher = createRedisConnection(); const lastDataClient = createRedisConnection(); diff --git a/packages/request-handler/package.json b/packages/request-handler/package.json index 0c3a0af88..bed276410 100644 --- a/packages/request-handler/package.json +++ b/packages/request-handler/package.json @@ -24,11 +24,11 @@ "dependencies": { "@extractus/feed-extractor": "7.1.7", "@homarr/common": "workspace:^0.1.0", + "@homarr/core": "workspace:^0.1.0", "@homarr/db": "workspace:^0.1.0", "@homarr/definitions": "workspace:^0.1.0", "@homarr/docker": "workspace:^0.1.0", "@homarr/integrations": "workspace:^0.1.0", - "@homarr/log": "workspace:^0.1.0", "@homarr/redis": "workspace:^0.1.0", "dayjs": "^1.11.19", "octokit": "^5.0.5", @@ -40,7 +40,7 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/request-handler/src/lib/cached-request-handler.ts b/packages/request-handler/src/lib/cached-request-handler.ts index 2e1739248..d9b8dfa90 100644 --- a/packages/request-handler/src/lib/cached-request-handler.ts +++ b/packages/request-handler/src/lib/cached-request-handler.ts @@ -1,9 +1,11 @@ import dayjs from "dayjs"; import type { Duration } from "dayjs/plugin/duration"; -import { logger } from "@homarr/log"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import type { createChannelWithLatestAndEvents } from "@homarr/redis"; +const logger = createLogger({ module: "cachedRequestHandler" }); + interface Options> { // Unique key for this request handler queryKey: string; @@ -34,9 +36,10 @@ export const createCachedRequestHandler = options.cacheDuration.asMilliseconds(); if (shouldRequestNewData) { - logger.debug( - `Cached request handler cache miss for channel='${channel.name}' queryKey='${options.queryKey}' reason='${!channelData ? "no data" : "cache expired"}'`, - ); + logger.debug("Cached request handler cache miss", { + channel: channel.name, + queryKey: options.queryKey, + reason: !channelData ? "no data" : "cache expired", + }); return await requestNewDataAsync(); } - logger.debug( - `Cached request handler cache hit for channel='${channel.name}' queryKey='${options.queryKey}' expiresAt='${dayjs(channelData.timestamp).add(options.cacheDuration).toISOString()}'`, - ); + logger.debug("Cached request handler cache hit", { + channel: channel.name, + queryKey: options.queryKey, + expiresAt: dayjs(channelData.timestamp).add(options.cacheDuration).toISOString(), + }); return channelData; }, async invalidateAsync() { - logger.debug( - `Cached request handler invalidating cache channel='${channel.name}' queryKey='${options.queryKey}'`, - ); + logger.debug("Cached request handler invalidating cache", { + channel: channel.name, + queryKey: options.queryKey, + }); await this.getCachedOrUpdatedDataAsync({ forceUpdate: true }); }, subscribe(callback: (data: TData) => void) { diff --git a/packages/request-handler/src/lib/cached-request-integration-job-handler.ts b/packages/request-handler/src/lib/cached-request-integration-job-handler.ts index 488aa6ee1..16bbf4b45 100644 --- a/packages/request-handler/src/lib/cached-request-integration-job-handler.ts +++ b/packages/request-handler/src/lib/cached-request-integration-job-handler.ts @@ -3,10 +3,11 @@ import SuperJSON from "superjson"; import { hashObjectBase64, Stopwatch } from "@homarr/common"; import { decryptSecret } from "@homarr/common/server"; import type { MaybeArray } from "@homarr/common/types"; +import { createLogger } from "@homarr/core/infrastructure/logs"; +import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; import { db } from "@homarr/db"; import { getItemsWithIntegrationsAsync, getServerSettingsAsync } from "@homarr/db/queries"; import type { WidgetKind } from "@homarr/definitions"; -import { logger } from "@homarr/log"; // This imports are done that way to avoid circular dependencies. import type { inferSupportedIntegrationsStrict } from "../../../widgets/src"; @@ -14,6 +15,8 @@ import { reduceWidgetOptionsWithDefaultValues } from "../../../widgets/src"; import type { WidgetComponentProps } from "../../../widgets/src/definition"; import type { createCachedIntegrationRequestHandler } from "./cached-integration-request-handler"; +const logger = createLogger({ module: "cachedRequestIntegrationJobHandler" }); + export const createRequestIntegrationJobHandler = < TWidgetKind extends WidgetKind, TIntegrationKind extends inferSupportedIntegrationsStrict, @@ -37,9 +40,10 @@ export const createRequestIntegrationJobHandler = < kinds: widgetKinds, }); - logger.debug( - `Found items for integration widgetKinds='${widgetKinds.join(",")}' count=${itemsForIntegration.length}`, - ); + logger.debug("Found items for integration", { + widgetKinds: widgetKinds.join(","), + count: itemsForIntegration.length, + }); const distinctIntegrations: { integrationId: string; @@ -97,14 +101,14 @@ export const createRequestIntegrationJobHandler = < ); const stopWatch = new Stopwatch(); await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: true }); - logger.debug( - `Ran integration job integration=${integrationId} inputHash='${inputHash}' elapsed=${stopWatch.getElapsedInHumanWords()}`, - ); + logger.debug("Ran integration job", { + integration: integrationId, + inputHash, + elapsed: stopWatch.getElapsedInHumanWords(), + }); } catch (error) { logger.error( - new Error(`Failed to run integration job integration=${integrationId} inputHash='${inputHash}'`, { - cause: error, - }), + new ErrorWithMetadata("Failed to run integration job", { integrationId, inputHash }, { cause: error }), ); } } diff --git a/packages/request-handler/src/minecraft-server-status.ts b/packages/request-handler/src/minecraft-server-status.ts index 4cff54ed8..f6c813193 100644 --- a/packages/request-handler/src/minecraft-server-status.ts +++ b/packages/request-handler/src/minecraft-server-status.ts @@ -1,7 +1,7 @@ import dayjs from "dayjs"; import { z } from "zod/v4"; -import { fetchWithTimeout } from "@homarr/common"; +import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout"; import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler"; @@ -11,7 +11,7 @@ export const minecraftServerStatusRequestHandler = createCachedWidgetRequestHand async requestAsync(input: { domain: string; isBedrockServer: boolean }) { const path = `${input.isBedrockServer ? "/bedrock" : ""}/3/${input.domain}`; - const response = await fetchWithTimeout(`https://api.mcsrvstat.us${path}`); + const response = await fetchWithTimeoutAsync(`https://api.mcsrvstat.us${path}`); return responseSchema.parse(await response.json()); }, cacheDuration: dayjs.duration(5, "minutes"), diff --git a/packages/request-handler/src/rss-feeds.ts b/packages/request-handler/src/rss-feeds.ts index 1efdcc151..e1c9f68c5 100644 --- a/packages/request-handler/src/rss-feeds.ts +++ b/packages/request-handler/src/rss-feeds.ts @@ -4,10 +4,12 @@ import dayjs from "dayjs"; import { z } from "zod/v4"; import type { Modify } from "@homarr/common/types"; -import { logger } from "@homarr/log"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler"; +const logger = createLogger({ module: "rssFeedsRequestHandler" }); + export const rssFeedsRequestHandler = createCachedWidgetRequestHandler({ queryKey: "rssFeedList", widgetKind: "rssFeed", diff --git a/packages/request-handler/src/stock-price.ts b/packages/request-handler/src/stock-price.ts index 420d26a6e..7b724c43a 100644 --- a/packages/request-handler/src/stock-price.ts +++ b/packages/request-handler/src/stock-price.ts @@ -1,7 +1,7 @@ import dayjs from "dayjs"; import { z } from "zod/v4"; -import { fetchWithTimeout } from "@homarr/common"; +import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout"; import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler"; @@ -9,7 +9,7 @@ export const fetchStockPriceHandler = createCachedWidgetRequestHandler({ queryKey: "fetchStockPriceResult", widgetKind: "stockPrice", async requestAsync(input: { stock: string; timeRange: string; timeInterval: string }) { - const response = await fetchWithTimeout( + const response = await fetchWithTimeoutAsync( `https://query1.finance.yahoo.com/v8/finance/chart/${input.stock}?range=${input.timeRange}&interval=${input.timeInterval}`, ); const data = dataSchema.parse(await response.json()); @@ -24,12 +24,16 @@ export const fetchStockPriceHandler = createCachedWidgetRequestHandler({ if (!firstResult) { throw new Error("Received invalid data"); } + + const priceHistory = + firstResult.indicators.quote[0]?.close.filter( + // Filter out null values from price arrays (Yahoo Finance returns null for missing data points) + (value) => value !== null && value !== undefined, + ) ?? []; + return { - priceHistory: - firstResult.indicators.quote[0]?.close.filter( - // Filter out null values from price arrays (Yahoo Finance returns null for missing data points) - (value) => value !== null && value !== undefined, - ) ?? [], + priceHistory, + previousClose: firstResult.meta.previousClose ?? priceHistory[0] ?? 1, symbol: firstResult.meta.symbol, shortName: firstResult.meta.shortName, }; @@ -58,6 +62,7 @@ const dataSchema = z meta: z.object({ symbol: z.string(), shortName: z.string(), + previousClose: z.number().optional(), }), }), ), diff --git a/packages/request-handler/src/update-checker.ts b/packages/request-handler/src/update-checker.ts index a12e63564..b2dca3b1e 100644 --- a/packages/request-handler/src/update-checker.ts +++ b/packages/request-handler/src/update-checker.ts @@ -2,14 +2,16 @@ import dayjs from "dayjs"; import { Octokit } from "octokit"; import { compareSemVer, isValidSemVer } from "semver-parser"; -import { fetchWithTimeout } from "@homarr/common"; import { env } from "@homarr/common/env"; -import { logger } from "@homarr/log"; +import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { createChannelWithLatestAndEvents } from "@homarr/redis"; import { createCachedRequestHandler } from "@homarr/request-handler/lib/cached-request-handler"; import packageJson from "../../../package.json"; +const logger = createLogger({ module: "updateCheckerRequestHandler" }); + export const updateCheckerRequestHandler = createCachedRequestHandler({ queryKey: "homarr-update-checker", cacheDuration: dayjs.duration(1, "hour"), @@ -21,7 +23,7 @@ export const updateCheckerRequestHandler = createCachedRequestHandler({ const octokit = new Octokit({ request: { - fetch: fetchWithTimeout, + fetch: fetchWithTimeoutAsync, }, }); const releases = await octokit.rest.repos.listReleases({ @@ -34,7 +36,7 @@ export const updateCheckerRequestHandler = createCachedRequestHandler({ for (const release of releases.data) { if (!isValidSemVer(release.tag_name)) { - logger.warn(`Unable to parse semantic tag '${release.tag_name}'. Update check might not work.`); + logger.warn("Unable to parse semantic tag. Update check might not work.", { tagName: release.tag_name }); continue; } @@ -46,11 +48,12 @@ export const updateCheckerRequestHandler = createCachedRequestHandler({ .sort((releaseA, releaseB) => compareSemVer(releaseB.tag_name, releaseA.tag_name)); if (availableNewerReleases.length > 0) { logger.info( + "Update checker found a new available version", // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - `Update checker found a new available version: ${availableReleases[0]!.tag_name}. Current version is ${currentVersion}`, + { version: availableReleases[0]!.tag_name, currentVersion }, ); } else { - logger.debug(`Update checker did not find any available updates. Current version is ${currentVersion}`); + logger.debug("Update checker did not find any available updates", { currentVersion }); } return { diff --git a/packages/request-handler/src/weather.ts b/packages/request-handler/src/weather.ts index faa97846c..fdf1af8f2 100644 --- a/packages/request-handler/src/weather.ts +++ b/packages/request-handler/src/weather.ts @@ -1,7 +1,7 @@ import dayjs from "dayjs"; import { z } from "zod"; -import { fetchWithTimeout } from "@homarr/common"; +import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout"; import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler"; @@ -9,7 +9,7 @@ export const weatherRequestHandler = createCachedWidgetRequestHandler({ queryKey: "weatherAtLocation", widgetKind: "weather", async requestAsync(input: { latitude: number; longitude: number }) { - const res = await fetchWithTimeout( + const res = await fetchWithTimeoutAsync( `https://api.open-meteo.com/v1/forecast?latitude=${input.latitude}&longitude=${input.longitude}&daily=weathercode,temperature_2m_max,temperature_2m_min,sunrise,sunset,wind_speed_10m_max,wind_gusts_10m_max¤t_weather=true&timezone=auto`, ); const json: unknown = await res.json(); diff --git a/packages/server-settings/package.json b/packages/server-settings/package.json index 3e1411ac2..c1b4ee200 100644 --- a/packages/server-settings/package.json +++ b/packages/server-settings/package.json @@ -29,7 +29,7 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/settings/package.json b/packages/settings/package.json index 632078325..50ad28369 100644 --- a/packages/settings/package.json +++ b/packages/settings/package.json @@ -26,16 +26,16 @@ "@homarr/api": "workspace:^0.1.0", "@homarr/db": "workspace:^0.1.0", "@homarr/server-settings": "workspace:^0.1.0", - "@mantine/dates": "^8.3.9", - "next": "16.0.7", - "react": "19.2.1", - "react-dom": "19.2.1" + "@mantine/dates": "^8.3.10", + "next": "16.0.10", + "react": "19.2.3", + "react-dom": "19.2.3" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/spotlight/package.json b/packages/spotlight/package.json index c213afcee..45437b968 100644 --- a/packages/spotlight/package.json +++ b/packages/spotlight/package.json @@ -33,21 +33,21 @@ "@homarr/settings": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0", "@homarr/ui": "workspace:^0.1.0", - "@mantine/core": "^8.3.9", - "@mantine/hooks": "^8.3.9", - "@mantine/spotlight": "^8.3.9", + "@mantine/core": "^8.3.10", + "@mantine/hooks": "^8.3.10", + "@mantine/spotlight": "^8.3.10", "@tabler/icons-react": "^3.35.0", - "jotai": "^2.15.2", - "next": "16.0.7", - "react": "19.2.1", - "react-dom": "19.2.1", + "jotai": "^2.16.0", + "next": "16.0.10", + "react": "19.2.3", + "react-dom": "19.2.3", "use-deep-compare-effect": "^1.8.1" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/translation/package.json b/packages/translation/package.json index f93589c71..86a78810e 100644 --- a/packages/translation/package.json +++ b/packages/translation/package.json @@ -32,16 +32,16 @@ "dayjs": "^1.11.19", "deepmerge": "4.3.1", "mantine-react-table": "2.0.0-beta.9", - "next": "16.0.7", - "next-intl": "4.5.8", - "react": "19.2.1", - "react-dom": "19.2.1" + "next": "16.0.10", + "next-intl": "4.6.0", + "react": "19.2.3", + "react-dom": "19.2.3" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/translation/src/lang/ca.json b/packages/translation/src/lang/ca.json index 8321b8820..251b05c24 100644 --- a/packages/translation/src/lang/ca.json +++ b/packages/translation/src/lang/ca.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "", "description": "", - "option": {}, + "option": { + "enableRowSorting": { + "label": "" + }, + "defaultSort": { + "label": "", + "option": { + "name": "", + "state": "", + "cpuUsage": "", + "memoryUsage": "" + } + }, + "descendingDefaultSort": { + "label": "" + } + }, "error": { "internalServerError": "" } diff --git a/packages/translation/src/lang/cn.json b/packages/translation/src/lang/cn.json index 4675cd5f9..e51894d33 100644 --- a/packages/translation/src/lang/cn.json +++ b/packages/translation/src/lang/cn.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "Docker 状态", "description": "您的容器状态 (这个小部件只能用管理员权限添加)", - "option": {}, + "option": { + "enableRowSorting": { + "label": "" + }, + "defaultSort": { + "label": "", + "option": { + "name": "", + "state": "", + "cpuUsage": "", + "memoryUsage": "" + } + }, + "descendingDefaultSort": { + "label": "" + } + }, "error": { "internalServerError": "获取容器统计信息失败" } diff --git a/packages/translation/src/lang/cr.json b/packages/translation/src/lang/cr.json index 1741a16a2..59bbea670 100644 --- a/packages/translation/src/lang/cr.json +++ b/packages/translation/src/lang/cr.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "crwdns3424:0crwdne3424:0", "description": "crwdns3426:0crwdne3426:0", - "option": {}, + "option": { + "enableRowSorting": { + "label": "crwdns3808:0crwdne3808:0" + }, + "defaultSort": { + "label": "crwdns3810:0crwdne3810:0", + "option": { + "name": "crwdns3812:0crwdne3812:0", + "state": "crwdns3814:0crwdne3814:0", + "cpuUsage": "crwdns3816:0crwdne3816:0", + "memoryUsage": "crwdns3818:0crwdne3818:0" + } + }, + "descendingDefaultSort": { + "label": "crwdns3820:0crwdne3820:0" + } + }, "error": { "internalServerError": "crwdns3428:0crwdne3428:0" } diff --git a/packages/translation/src/lang/cs.json b/packages/translation/src/lang/cs.json index 2f74a0679..4cfe8f384 100644 --- a/packages/translation/src/lang/cs.json +++ b/packages/translation/src/lang/cs.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "", "description": "", - "option": {}, + "option": { + "enableRowSorting": { + "label": "" + }, + "defaultSort": { + "label": "", + "option": { + "name": "", + "state": "", + "cpuUsage": "", + "memoryUsage": "" + } + }, + "descendingDefaultSort": { + "label": "" + } + }, "error": { "internalServerError": "" } diff --git a/packages/translation/src/lang/da.json b/packages/translation/src/lang/da.json index dc7da9370..1fc069004 100644 --- a/packages/translation/src/lang/da.json +++ b/packages/translation/src/lang/da.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "Docker statistik", "description": "Statistik for dine containere (Denne widget kan kun tilføjes med administratorrettigheder)", - "option": {}, + "option": { + "enableRowSorting": { + "label": "" + }, + "defaultSort": { + "label": "", + "option": { + "name": "", + "state": "", + "cpuUsage": "", + "memoryUsage": "" + } + }, + "descendingDefaultSort": { + "label": "" + } + }, "error": { "internalServerError": "Kunne ikke hente containerstatistik" } diff --git a/packages/translation/src/lang/de-CH.json b/packages/translation/src/lang/de-CH.json index fc241e31c..d2577803d 100644 --- a/packages/translation/src/lang/de-CH.json +++ b/packages/translation/src/lang/de-CH.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "", "description": "", - "option": {}, + "option": { + "enableRowSorting": { + "label": "" + }, + "defaultSort": { + "label": "", + "option": { + "name": "", + "state": "", + "cpuUsage": "", + "memoryUsage": "" + } + }, + "descendingDefaultSort": { + "label": "" + } + }, "error": { "internalServerError": "" } diff --git a/packages/translation/src/lang/de.json b/packages/translation/src/lang/de.json index eeb8d5922..334a2ff2b 100644 --- a/packages/translation/src/lang/de.json +++ b/packages/translation/src/lang/de.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "Docker Statistiken", "description": "Statistiken Ihrer Container (Dieses Widget kann nur mit Administratorrechten hinzugefügt werden)", - "option": {}, + "option": { + "enableRowSorting": { + "label": "Sortierung von Elementen aktivieren" + }, + "defaultSort": { + "label": "Standardmäßig verwendete Spalte zur Sortierung", + "option": { + "name": "Name", + "state": "Status", + "cpuUsage": "CPU-Auslastung", + "memoryUsage": "Speicherverbrauch" + } + }, + "descendingDefaultSort": { + "label": "Sortierung umkehren" + } + }, "error": { "internalServerError": "Fehler beim Abrufen der Container Statistiken" } @@ -2232,7 +2248,7 @@ "unknown": "Unbekannt", "pending": "Ausstehend", "processing": "In Bearbeitung", - "requested": "", + "requested": "Angefordert", "partiallyAvailable": "Teilweise", "available": "Verfügbar", "blacklisted": "Gesperrt", diff --git a/packages/translation/src/lang/el.json b/packages/translation/src/lang/el.json index 1cd7b54c7..01610b5cc 100644 --- a/packages/translation/src/lang/el.json +++ b/packages/translation/src/lang/el.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "", "description": "", - "option": {}, + "option": { + "enableRowSorting": { + "label": "" + }, + "defaultSort": { + "label": "", + "option": { + "name": "", + "state": "", + "cpuUsage": "", + "memoryUsage": "" + } + }, + "descendingDefaultSort": { + "label": "" + } + }, "error": { "internalServerError": "" } diff --git a/packages/translation/src/lang/en-gb.json b/packages/translation/src/lang/en-gb.json index c4f0631e4..f7bcf20cb 100644 --- a/packages/translation/src/lang/en-gb.json +++ b/packages/translation/src/lang/en-gb.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "", "description": "", - "option": {}, + "option": { + "enableRowSorting": { + "label": "" + }, + "defaultSort": { + "label": "", + "option": { + "name": "", + "state": "", + "cpuUsage": "", + "memoryUsage": "" + } + }, + "descendingDefaultSort": { + "label": "" + } + }, "error": { "internalServerError": "" } diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json index 82c3bdcfd..517ecfea1 100644 --- a/packages/translation/src/lang/en.json +++ b/packages/translation/src/lang/en.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "Docker stats", "description": "Stats of your containers (This widget can only be added with administrator privileges)", - "option": {}, + "option": { + "enableRowSorting": { + "label": "Enable items sorting" + }, + "defaultSort": { + "label": "Column used for sorting by default", + "option": { + "name": "Name", + "state": "State", + "cpuUsage": "CPU usage", + "memoryUsage": "Memory usage" + } + }, + "descendingDefaultSort": { + "label": "Invert sorting" + } + }, "error": { "internalServerError": "Failed to fetch containers stats" } diff --git a/packages/translation/src/lang/es.json b/packages/translation/src/lang/es.json index 684512956..ae157ab9f 100644 --- a/packages/translation/src/lang/es.json +++ b/packages/translation/src/lang/es.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "Estadísticas de Docker", "description": "Estadísticas de tus contenedores (Este widget sólo puede ser añadido con privilegios de administrador)", - "option": {}, + "option": { + "enableRowSorting": { + "label": "" + }, + "defaultSort": { + "label": "", + "option": { + "name": "", + "state": "", + "cpuUsage": "", + "memoryUsage": "" + } + }, + "descendingDefaultSort": { + "label": "" + } + }, "error": { "internalServerError": "No se pudo obtener las estadísticas de los contenedores" } diff --git a/packages/translation/src/lang/et.json b/packages/translation/src/lang/et.json index d07675b22..35a0c3430 100644 --- a/packages/translation/src/lang/et.json +++ b/packages/translation/src/lang/et.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "", "description": "", - "option": {}, + "option": { + "enableRowSorting": { + "label": "" + }, + "defaultSort": { + "label": "", + "option": { + "name": "", + "state": "", + "cpuUsage": "", + "memoryUsage": "" + } + }, + "descendingDefaultSort": { + "label": "" + } + }, "error": { "internalServerError": "" } diff --git a/packages/translation/src/lang/fr.json b/packages/translation/src/lang/fr.json index bf3620771..cf026c16e 100644 --- a/packages/translation/src/lang/fr.json +++ b/packages/translation/src/lang/fr.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "Statistiques de Docker", "description": "Statistiques de vos conteneurs (Ce widget ne peut être ajouté qu'avec les privilèges d'administrateur)", - "option": {}, + "option": { + "enableRowSorting": { + "label": "" + }, + "defaultSort": { + "label": "", + "option": { + "name": "", + "state": "", + "cpuUsage": "", + "memoryUsage": "" + } + }, + "descendingDefaultSort": { + "label": "" + } + }, "error": { "internalServerError": "Impossible de récupérer les statistiques des conteneurs" } diff --git a/packages/translation/src/lang/he.json b/packages/translation/src/lang/he.json index d96129e80..38c09298e 100644 --- a/packages/translation/src/lang/he.json +++ b/packages/translation/src/lang/he.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "סטטיסטיקות דוקר", "description": "סטטיסטיקות של המכולות שלך (ניתן להוסיף ווידג'ט זה רק עם הרשאות מנהל)", - "option": {}, + "option": { + "enableRowSorting": { + "label": "אפשר מיון פריטים" + }, + "defaultSort": { + "label": "עמודה המשמשת למיון כברירת מחדל", + "option": { + "name": "שם", + "state": "מצב", + "cpuUsage": "ניצול מעבד", + "memoryUsage": "ניצול זיכרון" + } + }, + "descendingDefaultSort": { + "label": "מיון הפוך" + } + }, "error": { "internalServerError": "נכשל באחזור סטטיסטיקות המכולות" } @@ -2232,7 +2248,7 @@ "unknown": "לא ידוע", "pending": "בהמתנה", "processing": "מעבד", - "requested": "", + "requested": "נדרש", "partiallyAvailable": "חלקי", "available": "זמין", "blacklisted": "ברשימה השחורה", @@ -3024,8 +3040,8 @@ "authorization": "הרשאה" }, "heroBanner": { - "title": "", - "subtitle": "" + "title": "ברוך שובך ל-", + "subtitle": "לוח {app}" } }, "board": { @@ -3383,7 +3399,7 @@ "label": "ממשקי חומת אש" }, "weather": { - "label": "" + "label": "מזג אוויר" } }, "interval": { @@ -3511,19 +3527,19 @@ "subtitle": "{count} בשימוש בקוד של Homarr" }, "hotkeys": { - "title": "", - "subtitle": "", + "title": "מקשי קיצור", + "subtitle": "קיצורי מקלדת לשיפור זרימת העבודה שלך", "field": { - "shortcut": "", - "action": "" + "shortcut": "קיצור דרך", + "action": "פעולה" }, "action": { - "toggleBoardEdit": "", - "toggleColorScheme": "", - "saveNotebook": "", - "openSpotlight": "" + "toggleBoardEdit": "הפעל/י מצב עריכת לוח", + "toggleColorScheme": "החלפת מצב בהיר/כהה", + "saveNotebook": "שמירת מחברת (רק בתוך ווידג'ט המחברת)", + "openSpotlight": "פתח חיפוש" }, - "note": "" + "note": "טיפ: Mod מתייחס גם למקש Ctrl וגם למקש ⌘ ב-macOS" } } } diff --git a/packages/translation/src/lang/hr.json b/packages/translation/src/lang/hr.json index 3ffb32c4d..d95f0d554 100644 --- a/packages/translation/src/lang/hr.json +++ b/packages/translation/src/lang/hr.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "", "description": "", - "option": {}, + "option": { + "enableRowSorting": { + "label": "" + }, + "defaultSort": { + "label": "", + "option": { + "name": "", + "state": "", + "cpuUsage": "", + "memoryUsage": "" + } + }, + "descendingDefaultSort": { + "label": "" + } + }, "error": { "internalServerError": "" } diff --git a/packages/translation/src/lang/hu.json b/packages/translation/src/lang/hu.json index 3b665b581..5069d1763 100644 --- a/packages/translation/src/lang/hu.json +++ b/packages/translation/src/lang/hu.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "", "description": "", - "option": {}, + "option": { + "enableRowSorting": { + "label": "" + }, + "defaultSort": { + "label": "", + "option": { + "name": "", + "state": "", + "cpuUsage": "", + "memoryUsage": "" + } + }, + "descendingDefaultSort": { + "label": "" + } + }, "error": { "internalServerError": "" } diff --git a/packages/translation/src/lang/it.json b/packages/translation/src/lang/it.json index 3c7e541b1..990a7db31 100644 --- a/packages/translation/src/lang/it.json +++ b/packages/translation/src/lang/it.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "Statistiche del docker", "description": "Statistiche dei tuoi contenitori (questo widget può essere aggiunto solo con privilegi di amministratore)", - "option": {}, + "option": { + "enableRowSorting": { + "label": "Abilita l'ordinamento degli elementi" + }, + "defaultSort": { + "label": "Colonna utilizzata per l'ordinamento predefinito", + "option": { + "name": "Nome", + "state": "Stato", + "cpuUsage": "Uso della CPU", + "memoryUsage": "Uso della memoria" + } + }, + "descendingDefaultSort": { + "label": "Inverti ordinamento" + } + }, "error": { "internalServerError": "Recupero statistiche container non riuscito" } diff --git a/packages/translation/src/lang/ja.json b/packages/translation/src/lang/ja.json index 78182a513..1a5d961ea 100644 --- a/packages/translation/src/lang/ja.json +++ b/packages/translation/src/lang/ja.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "Dockerのステータス", "description": "あなたのコンテナの統計(このウィジェットは管理者権限でのみ追加できます)", - "option": {}, + "option": { + "enableRowSorting": { + "label": "" + }, + "defaultSort": { + "label": "", + "option": { + "name": "", + "state": "", + "cpuUsage": "", + "memoryUsage": "" + } + }, + "descendingDefaultSort": { + "label": "" + } + }, "error": { "internalServerError": "コンテナステータスの取得に失敗しました" } diff --git a/packages/translation/src/lang/ko.json b/packages/translation/src/lang/ko.json index 6ff129146..4c8d60e7b 100644 --- a/packages/translation/src/lang/ko.json +++ b/packages/translation/src/lang/ko.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "", "description": "", - "option": {}, + "option": { + "enableRowSorting": { + "label": "" + }, + "defaultSort": { + "label": "", + "option": { + "name": "", + "state": "", + "cpuUsage": "", + "memoryUsage": "" + } + }, + "descendingDefaultSort": { + "label": "" + } + }, "error": { "internalServerError": "" } diff --git a/packages/translation/src/lang/lt.json b/packages/translation/src/lang/lt.json index 94488d9ca..c13cf090a 100644 --- a/packages/translation/src/lang/lt.json +++ b/packages/translation/src/lang/lt.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "", "description": "", - "option": {}, + "option": { + "enableRowSorting": { + "label": "" + }, + "defaultSort": { + "label": "", + "option": { + "name": "", + "state": "", + "cpuUsage": "", + "memoryUsage": "" + } + }, + "descendingDefaultSort": { + "label": "" + } + }, "error": { "internalServerError": "" } diff --git a/packages/translation/src/lang/lv.json b/packages/translation/src/lang/lv.json index 3a3f00a1d..67d38b028 100644 --- a/packages/translation/src/lang/lv.json +++ b/packages/translation/src/lang/lv.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "", "description": "", - "option": {}, + "option": { + "enableRowSorting": { + "label": "" + }, + "defaultSort": { + "label": "", + "option": { + "name": "", + "state": "", + "cpuUsage": "", + "memoryUsage": "" + } + }, + "descendingDefaultSort": { + "label": "" + } + }, "error": { "internalServerError": "" } diff --git a/packages/translation/src/lang/nl.json b/packages/translation/src/lang/nl.json index da8036109..702646e08 100644 --- a/packages/translation/src/lang/nl.json +++ b/packages/translation/src/lang/nl.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "Docker statistieken", "description": "", - "option": {}, + "option": { + "enableRowSorting": { + "label": "" + }, + "defaultSort": { + "label": "", + "option": { + "name": "", + "state": "", + "cpuUsage": "", + "memoryUsage": "" + } + }, + "descendingDefaultSort": { + "label": "" + } + }, "error": { "internalServerError": "" } diff --git a/packages/translation/src/lang/no.json b/packages/translation/src/lang/no.json index dfea4bdb9..8254597a5 100644 --- a/packages/translation/src/lang/no.json +++ b/packages/translation/src/lang/no.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "", "description": "", - "option": {}, + "option": { + "enableRowSorting": { + "label": "" + }, + "defaultSort": { + "label": "", + "option": { + "name": "", + "state": "", + "cpuUsage": "", + "memoryUsage": "" + } + }, + "descendingDefaultSort": { + "label": "" + } + }, "error": { "internalServerError": "" } diff --git a/packages/translation/src/lang/pl.json b/packages/translation/src/lang/pl.json index bb1587c60..fb3d10537 100644 --- a/packages/translation/src/lang/pl.json +++ b/packages/translation/src/lang/pl.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "Statystyki Dockera", "description": "Statystyki Twoich kontenerów (ten widżet można dodać tylko z uprawnieniami administratora)", - "option": {}, + "option": { + "enableRowSorting": { + "label": "" + }, + "defaultSort": { + "label": "", + "option": { + "name": "", + "state": "", + "cpuUsage": "", + "memoryUsage": "" + } + }, + "descendingDefaultSort": { + "label": "" + } + }, "error": { "internalServerError": "Nie udało się pobrać statystyk kontenerów" } diff --git a/packages/translation/src/lang/pt.json b/packages/translation/src/lang/pt.json index 76a5e0fe8..5d041362f 100644 --- a/packages/translation/src/lang/pt.json +++ b/packages/translation/src/lang/pt.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "", "description": "", - "option": {}, + "option": { + "enableRowSorting": { + "label": "" + }, + "defaultSort": { + "label": "", + "option": { + "name": "", + "state": "", + "cpuUsage": "", + "memoryUsage": "" + } + }, + "descendingDefaultSort": { + "label": "" + } + }, "error": { "internalServerError": "" } diff --git a/packages/translation/src/lang/ro.json b/packages/translation/src/lang/ro.json index 86ae712cd..78d495554 100644 --- a/packages/translation/src/lang/ro.json +++ b/packages/translation/src/lang/ro.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "", "description": "", - "option": {}, + "option": { + "enableRowSorting": { + "label": "" + }, + "defaultSort": { + "label": "", + "option": { + "name": "", + "state": "", + "cpuUsage": "", + "memoryUsage": "" + } + }, + "descendingDefaultSort": { + "label": "" + } + }, "error": { "internalServerError": "" } diff --git a/packages/translation/src/lang/ru.json b/packages/translation/src/lang/ru.json index 08f5ff335..a2204f0aa 100644 --- a/packages/translation/src/lang/ru.json +++ b/packages/translation/src/lang/ru.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "", "description": "", - "option": {}, + "option": { + "enableRowSorting": { + "label": "" + }, + "defaultSort": { + "label": "", + "option": { + "name": "", + "state": "", + "cpuUsage": "", + "memoryUsage": "" + } + }, + "descendingDefaultSort": { + "label": "" + } + }, "error": { "internalServerError": "" } diff --git a/packages/translation/src/lang/sk.json b/packages/translation/src/lang/sk.json index f3f105502..482696ee7 100644 --- a/packages/translation/src/lang/sk.json +++ b/packages/translation/src/lang/sk.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "Docker stav", "description": "Štatistiky vašich kontajnerov (Túto miniaplikáciu je možné pridať iba s oprávneniami správcu)", - "option": {}, + "option": { + "enableRowSorting": { + "label": "" + }, + "defaultSort": { + "label": "", + "option": { + "name": "", + "state": "", + "cpuUsage": "", + "memoryUsage": "" + } + }, + "descendingDefaultSort": { + "label": "" + } + }, "error": { "internalServerError": "Nepodarilo sa načítať štatistiky kontajnerov" } diff --git a/packages/translation/src/lang/sl.json b/packages/translation/src/lang/sl.json index 6a3d0ae96..194997b1d 100644 --- a/packages/translation/src/lang/sl.json +++ b/packages/translation/src/lang/sl.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "", "description": "", - "option": {}, + "option": { + "enableRowSorting": { + "label": "" + }, + "defaultSort": { + "label": "", + "option": { + "name": "", + "state": "", + "cpuUsage": "", + "memoryUsage": "" + } + }, + "descendingDefaultSort": { + "label": "" + } + }, "error": { "internalServerError": "" } diff --git a/packages/translation/src/lang/sv.json b/packages/translation/src/lang/sv.json index 71fde14e9..fd81d0ae6 100644 --- a/packages/translation/src/lang/sv.json +++ b/packages/translation/src/lang/sv.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "", "description": "", - "option": {}, + "option": { + "enableRowSorting": { + "label": "" + }, + "defaultSort": { + "label": "", + "option": { + "name": "", + "state": "", + "cpuUsage": "", + "memoryUsage": "" + } + }, + "descendingDefaultSort": { + "label": "" + } + }, "error": { "internalServerError": "" } diff --git a/packages/translation/src/lang/tr.json b/packages/translation/src/lang/tr.json index 6a893b69c..628775c28 100644 --- a/packages/translation/src/lang/tr.json +++ b/packages/translation/src/lang/tr.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "Docker istatistikleri", "description": "Konteynerlerinizin istatistikleri (Bu widget yalnızca yönetici ayrıcalıklarıyla eklenebilir)", - "option": {}, + "option": { + "enableRowSorting": { + "label": "" + }, + "defaultSort": { + "label": "", + "option": { + "name": "", + "state": "", + "cpuUsage": "", + "memoryUsage": "" + } + }, + "descendingDefaultSort": { + "label": "" + } + }, "error": { "internalServerError": "Konteyner istatistikleri alınamadı" } diff --git a/packages/translation/src/lang/uk.json b/packages/translation/src/lang/uk.json index 4851d956e..e1f8194d2 100644 --- a/packages/translation/src/lang/uk.json +++ b/packages/translation/src/lang/uk.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "", "description": "", - "option": {}, + "option": { + "enableRowSorting": { + "label": "" + }, + "defaultSort": { + "label": "", + "option": { + "name": "", + "state": "", + "cpuUsage": "", + "memoryUsage": "" + } + }, + "descendingDefaultSort": { + "label": "" + } + }, "error": { "internalServerError": "" } diff --git a/packages/translation/src/lang/vi.json b/packages/translation/src/lang/vi.json index 122338f7f..0eb514480 100644 --- a/packages/translation/src/lang/vi.json +++ b/packages/translation/src/lang/vi.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "", "description": "", - "option": {}, + "option": { + "enableRowSorting": { + "label": "" + }, + "defaultSort": { + "label": "", + "option": { + "name": "", + "state": "", + "cpuUsage": "", + "memoryUsage": "" + } + }, + "descendingDefaultSort": { + "label": "" + } + }, "error": { "internalServerError": "" } diff --git a/packages/translation/src/lang/zh.json b/packages/translation/src/lang/zh.json index f4bb1322c..518ab5634 100644 --- a/packages/translation/src/lang/zh.json +++ b/packages/translation/src/lang/zh.json @@ -1939,7 +1939,23 @@ "dockerContainers": { "name": "", "description": "", - "option": {}, + "option": { + "enableRowSorting": { + "label": "" + }, + "defaultSort": { + "label": "", + "option": { + "name": "", + "state": "", + "cpuUsage": "", + "memoryUsage": "" + } + }, + "descendingDefaultSort": { + "label": "" + } + }, "error": { "internalServerError": "" } diff --git a/packages/ui/package.json b/packages/ui/package.json index 138767901..5663d2d4f 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -27,17 +27,16 @@ "dependencies": { "@homarr/common": "workspace:^0.1.0", "@homarr/definitions": "workspace:^0.1.0", - "@homarr/log": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0", - "@mantine/core": "^8.3.9", - "@mantine/dates": "^8.3.9", - "@mantine/hooks": "^8.3.9", + "@mantine/core": "^8.3.10", + "@mantine/dates": "^8.3.10", + "@mantine/hooks": "^8.3.10", "@tabler/icons-react": "^3.35.0", "mantine-react-table": "2.0.0-beta.9", - "next": "16.0.7", - "react": "19.2.1", - "react-dom": "19.2.1", + "next": "16.0.10", + "react": "19.2.3", + "react-dom": "19.2.3", "svgson": "^5.3.1" }, "devDependencies": { @@ -45,7 +44,7 @@ "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", "@types/css-modules": "^1.0.5", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/validation/package.json b/packages/validation/package.json index 5bfbaae16..a1e5bc76c 100644 --- a/packages/validation/package.json +++ b/packages/validation/package.json @@ -31,7 +31,7 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/widgets/package.json b/packages/widgets/package.json index 9b61a9d67..0a8083ac4 100644 --- a/packages/widgets/package.json +++ b/packages/widgets/package.json @@ -31,13 +31,13 @@ "@homarr/auth": "workspace:^0.1.0", "@homarr/boards": "workspace:^0.1.0", "@homarr/common": "workspace:^0.1.0", + "@homarr/core": "workspace:^0.1.0", "@homarr/db": "workspace:^0.1.0", "@homarr/definitions": "workspace:^0.1.0", "@homarr/docker": "workspace:^0.1.0", "@homarr/form": "workspace:^0.1.0", "@homarr/forms-collection": "workspace:^0.1.0", "@homarr/integrations": "workspace:^0.1.0", - "@homarr/log": "workspace:^0.1.0", "@homarr/modals": "workspace:^0.1.0", "@homarr/modals-collection": "workspace:^0.1.0", "@homarr/notifications": "workspace:^0.1.0", @@ -48,33 +48,33 @@ "@homarr/translation": "workspace:^0.1.0", "@homarr/ui": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0", - "@mantine/charts": "^8.3.9", - "@mantine/core": "^8.3.9", - "@mantine/hooks": "^8.3.9", + "@mantine/charts": "^8.3.10", + "@mantine/core": "^8.3.10", + "@mantine/hooks": "^8.3.10", "@tabler/icons-react": "^3.35.0", - "@tiptap/extension-color": "2.27.1", - "@tiptap/extension-highlight": "2.27.1", - "@tiptap/extension-image": "2.27.1", - "@tiptap/extension-link": "^2.27.1", - "@tiptap/extension-placeholder": "^2.27.1", - "@tiptap/extension-table": "2.27.1", - "@tiptap/extension-table-cell": "2.27.1", - "@tiptap/extension-table-header": "2.27.1", - "@tiptap/extension-table-row": "2.27.1", - "@tiptap/extension-task-item": "2.27.1", - "@tiptap/extension-task-list": "2.27.1", - "@tiptap/extension-text-align": "2.27.1", - "@tiptap/extension-text-style": "2.27.1", - "@tiptap/extension-underline": "2.27.1", - "@tiptap/react": "^2.27.1", - "@tiptap/starter-kit": "^2.27.1", + "@tiptap/extension-color": "3.13.0", + "@tiptap/extension-highlight": "3.13.0", + "@tiptap/extension-image": "3.13.0", + "@tiptap/extension-link": "^3.13.0", + "@tiptap/extension-placeholder": "^3.13.0", + "@tiptap/extension-table": "3.13.0", + "@tiptap/extension-table-cell": "3.13.0", + "@tiptap/extension-table-header": "3.13.0", + "@tiptap/extension-table-row": "3.13.0", + "@tiptap/extension-task-item": "3.13.0", + "@tiptap/extension-task-list": "3.13.0", + "@tiptap/extension-text-align": "3.13.0", + "@tiptap/extension-text-style": "3.13.0", + "@tiptap/extension-underline": "3.13.0", + "@tiptap/react": "^3.13.0", + "@tiptap/starter-kit": "^3.13.0", "clsx": "^2.1.1", "dayjs": "^1.11.19", "mantine-form-zod-resolver": "^1.3.0", "mantine-react-table": "2.0.0-beta.9", - "next": "16.0.7", - "react": "19.2.1", - "react-dom": "19.2.1", + "next": "16.0.10", + "react": "19.2.3", + "react-dom": "19.2.3", "react-markdown": "^10.1.0", "recharts": "^2.15.4", "video.js": "^8.23.4", @@ -85,7 +85,7 @@ "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", "@types/video.js": "^7.3.58", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/packages/widgets/src/app/prefetch.ts b/packages/widgets/src/app/prefetch.ts index c1d148a6b..45300197b 100644 --- a/packages/widgets/src/app/prefetch.ts +++ b/packages/widgets/src/app/prefetch.ts @@ -1,10 +1,12 @@ import { trpc } from "@homarr/api/server"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { db, inArray } from "@homarr/db"; import { apps } from "@homarr/db/schema"; -import { logger } from "@homarr/log"; import type { Prefetch } from "../definition"; +const logger = createLogger({ module: "appWidgetPrefetch" }); + const prefetchAllAsync: Prefetch<"app"> = async (queryClient, items) => { const appIds = items.map((item) => item.options.appId); const distinctAppIds = [...new Set(appIds)]; @@ -17,7 +19,7 @@ const prefetchAllAsync: Prefetch<"app"> = async (queryClient, items) => { queryClient.setQueryData(trpc.app.byId.queryKey({ id: app.id }), app); } - logger.info(`Successfully prefetched ${dbApps.length} apps for app widget`); + logger.info("Successfully prefetched apps for app widget", { count: dbApps.length }); }; export default prefetchAllAsync; diff --git a/packages/widgets/src/bookmarks/prefetch.ts b/packages/widgets/src/bookmarks/prefetch.ts index 1da24c6ed..2c3970327 100644 --- a/packages/widgets/src/bookmarks/prefetch.ts +++ b/packages/widgets/src/bookmarks/prefetch.ts @@ -1,10 +1,12 @@ import { trpc } from "@homarr/api/server"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { db, inArray } from "@homarr/db"; import { apps } from "@homarr/db/schema"; -import { logger } from "@homarr/log"; import type { Prefetch } from "../definition"; +const logger = createLogger({ module: "bookmarksWidgetPrefetch" }); + const prefetchAllAsync: Prefetch<"bookmarks"> = async (queryClient, items) => { const appIds = items.flatMap((item) => item.options.items); const distinctAppIds = [...new Set(appIds)]; @@ -24,7 +26,7 @@ const prefetchAllAsync: Prefetch<"bookmarks"> = async (queryClient, items) => { ); } - logger.info(`Successfully prefetched ${dbApps.length} apps for bookmarks`); + logger.info("Successfully prefetched apps for bookmarks", { count: dbApps.length }); }; export default prefetchAllAsync; diff --git a/packages/widgets/src/calendar/calender-day.tsx b/packages/widgets/src/calendar/calender-day.tsx index 3629a937f..790cc0a82 100644 --- a/packages/widgets/src/calendar/calender-day.tsx +++ b/packages/widgets/src/calendar/calender-day.tsx @@ -1,5 +1,6 @@ import { useState } from "react"; import { Box, Container, Flex, Popover, Text, useMantineTheme } from "@mantine/core"; +import { useElementSize } from "@mantine/hooks"; import { useRequiredBoard } from "@homarr/boards/context"; import type { CalendarEvent } from "@homarr/integrations/types"; @@ -17,6 +18,7 @@ interface CalendarDayProps { export const CalendarDay = ({ date, events, disabled, rootHeight, rootWidth }: CalendarDayProps) => { const [opened, setOpened] = useState(false); const { primaryColor } = useMantineTheme(); + const { ref, height } = useElementSize(); const board = useRequiredBoard(); const mantineTheme = useMantineTheme(); const actualItemRadius = mantineTheme.radius[board.itemRadius]; @@ -25,6 +27,8 @@ export const CalendarDay = ({ date, events, disabled, rootHeight, rootWidth }: C const shouldScaleDown = minAxisSize < 350; const isSmall = rootHeight < 256; + const isTooSmallForIndicators = height < 30; + return ( {date.getDate()} - + {!isTooSmallForIndicators && } {/* Popover has some offset on the left side, padding is removed because of scrollarea paddings */} @@ -82,9 +87,19 @@ const NotificationIndicator = ({ events, isSmall }: NotificationIndicatorProps) const notificationEvents = [...new Set(events.map((event) => event.indicatorColor))].filter(String); /* position bottom is lower when small to not be on top of number*/ return ( - + {notificationEvents.map((notificationEvent) => { - return ; + return ; })} ); diff --git a/packages/widgets/src/calendar/component.tsx b/packages/widgets/src/calendar/component.tsx index 1677b2ba2..b287d2324 100644 --- a/packages/widgets/src/calendar/component.tsx +++ b/packages/widgets/src/calendar/component.tsx @@ -124,12 +124,17 @@ const CalendarBase = ({ isEditMode, events, month, setMonth, options }: Calendar }, monthCell: { textAlign: "center", + position: "relative", }, day: { borderRadius: actualItemRadius, width: "100%", height: "100%", - position: "relative", + position: "absolute", + top: 0, + left: 0, + bottom: 0, + right: 0, }, month: { height: "100%", @@ -137,6 +142,9 @@ const CalendarBase = ({ isEditMode, events, month, setMonth, options }: Calendar weekday: { padding: 0, }, + weekdaysRow: { + height: 22, + }, }} renderDay={(tileDate) => { const eventsForDate = normalizedEvents diff --git a/packages/widgets/src/docker/component.tsx b/packages/widgets/src/docker/component.tsx index 0687b7f18..869b76f0b 100644 --- a/packages/widgets/src/docker/component.tsx +++ b/packages/widgets/src/docker/component.tsx @@ -56,6 +56,7 @@ const createColumns = ( t: ReturnType>, ): MRT_ColumnDef[] => [ { + id: "name", accessorKey: "name", header: t("field.name.label"), Cell({ renderedCellValue, row }) { @@ -70,6 +71,7 @@ const createColumns = ( }, }, { + id: "state", accessorKey: "state", size: 100, header: t("field.state.label"), @@ -78,6 +80,13 @@ const createColumns = ( }, }, { + id: "cpuUsage", + sortingFn: (rowA, rowB) => { + const cpuUsageA = safeValue(rowA.original.cpuUsage); + const cpuUsageB = safeValue(rowB.original.cpuUsage); + + return cpuUsageA - cpuUsageB; + }, accessorKey: "cpuUsage", size: 80, header: t("field.stats.cpu.label"), @@ -92,6 +101,13 @@ const createColumns = ( }, }, { + id: "memoryUsage", + sortingFn: (rowA, rowB) => { + const memoryUsageA = safeValue(rowA.original.memoryUsage); + const memoryUsageB = safeValue(rowB.original.memoryUsage); + + return memoryUsageA - memoryUsageB; + }, accessorKey: "memoryUsage", size: 80, header: t("field.stats.memory.label"), @@ -106,9 +122,11 @@ const createColumns = ( }, }, { + id: "actions", accessorKey: "actions", size: 80, header: t("action.title"), + enableSorting: false, Cell({ row }) { const utils = clientApi.useUtils(); // eslint-disable-next-line no-restricted-syntax @@ -168,7 +186,7 @@ const createColumns = ( }, ]; -export default function DockerWidget({ width }: WidgetComponentProps<"dockerContainers">) { +export default function DockerWidget({ options, width, isEditMode }: WidgetComponentProps<"dockerContainers">) { const t = useScopedI18n("docker"); const isTiny = width <= 256; @@ -192,8 +210,8 @@ export default function DockerWidget({ width }: WidgetComponentProps<"dockerCont enablePagination: false, enableTopToolbar: false, enableBottomToolbar: false, - enableSorting: false, enableColumnActions: false, + enableSorting: options.enableRowSorting && !isEditMode, enableStickyHeader: false, enableColumnOrdering: false, enableRowSelection: false, @@ -203,6 +221,7 @@ export default function DockerWidget({ width }: WidgetComponentProps<"dockerCont enableFilters: false, enableHiding: false, initialState: { + sorting: [{ id: options.defaultSort, desc: options.descendingDefaultSort }], density: "xs", }, mantinePaperProps: { diff --git a/packages/widgets/src/docker/index.ts b/packages/widgets/src/docker/index.ts index fa738023a..1a0964af3 100644 --- a/packages/widgets/src/docker/index.ts +++ b/packages/widgets/src/docker/index.ts @@ -1,12 +1,35 @@ import { IconBrandDocker, IconServerOff } from "@tabler/icons-react"; +import type { RouterOutputs } from "@homarr/api"; + import { createWidgetDefinition } from "../definition"; import { optionsBuilder } from "../options"; +const columnsList = [ + "name", + "state", + "cpuUsage", + "memoryUsage", +] as const satisfies (keyof RouterOutputs["docker"]["getContainers"]["containers"][number])[]; + export const { definition, componentLoader } = createWidgetDefinition("dockerContainers", { icon: IconBrandDocker, createOptions() { - return optionsBuilder.from(() => ({})); + return optionsBuilder.from((factory) => ({ + enableRowSorting: factory.switch({ + defaultValue: false, + }), + defaultSort: factory.select({ + defaultValue: "name", + options: columnsList.map((value) => ({ + value, + label: (t) => t(`widget.dockerContainers.option.defaultSort.option.${value}`), + })), + }), + descendingDefaultSort: factory.switch({ + defaultValue: false, + }), + })); }, errors: { INTERNAL_SERVER_ERROR: { diff --git a/packages/widgets/src/downloads/component.tsx b/packages/widgets/src/downloads/component.tsx index c875855ed..9af29da6d 100644 --- a/packages/widgets/src/downloads/component.tsx +++ b/packages/widgets/src/downloads/component.tsx @@ -36,6 +36,7 @@ import { IconX, } from "@tabler/icons-react"; import dayjs from "dayjs"; +import relativeTime from "dayjs/plugin/relativeTime"; import type { MRT_ColumnDef, MRT_VisibilityState } from "mantine-react-table"; import { MantineReactTable, useMantineReactTable } from "mantine-react-table"; @@ -48,6 +49,8 @@ import { useScopedI18n } from "@homarr/translation/client"; import type { WidgetComponentProps } from "../definition"; +dayjs.extend(relativeTime); + interface QuickFilter { integrationKinds: string[]; statuses: ExtendedDownloadClientItem["state"][]; @@ -188,14 +191,14 @@ export default function DownloadClientsWidget({ ) //Add extrapolated data and actions if user is allowed interaction .map((item): ExtendedDownloadClientItem => { - const received = Math.floor(item.size * item.progress); + const received = item.received ?? Math.floor(item.size * item.progress); const integrationIds = [pair.integration.id]; return { integration: pair.integration, ...item, category: item.category !== undefined && item.category.length > 0 ? item.category : undefined, received, - ratio: item.sent !== undefined ? item.sent / (received || 1) : undefined, + ratio: item.sent !== undefined ? item.sent / item.size : undefined, //Only add if permission to use mutations actions: integrationsWithInteractions.includes(pair.integration.id) ? { @@ -470,13 +473,16 @@ export default function DownloadClientsWidget({ return ( - {new Intl.NumberFormat("en", { style: "percent", notation: "compact", unitDisplay: "narrow" }).format( - progress, - )} + {new Intl.NumberFormat("en", { + style: "percent", + notation: "compact", + unitDisplay: "narrow", + roundingMode: "floor", + }).format(progress)} @@ -714,7 +720,10 @@ const ItemInfoModal = ({ items, currentIndex, opened, onClose }: ItemInfoModalPr /> {item.type !== "miscellaneous" && } - + )} diff --git a/packages/widgets/src/iframe/component.tsx b/packages/widgets/src/iframe/component.tsx index 760c46b03..67fa73536 100644 --- a/packages/widgets/src/iframe/component.tsx +++ b/packages/widgets/src/iframe/component.tsx @@ -27,7 +27,7 @@ export default function IFrameWidget({ options, isEditMode }: WidgetComponentPro className={classes.iframe} src={embedUrl} title="widget iframe" - allow={allowedPermissions.join(" ")} + allow={allowedPermissions} scrolling={allowScrolling ? "yes" : "no"} sandbox={sandboxFlags.join(" ")} > @@ -77,9 +77,13 @@ const UnsupportedProtocol = () => { const getAllowedPermissions = ( permissions: Omit["options"], "embedUrl" | "allowScrolling">, ) => { - return objectEntries(permissions) - .filter(([_key, value]) => value) - .map(([key]) => permissionMapping[key]); + return ( + objectEntries(permissions) + .filter(([_key, value]) => value) + // * means it applies to all origins + .map(([key]) => `${permissionMapping[key]} *`) + .join("; ") + ); }; const getSandboxFlags = ( diff --git a/packages/widgets/src/notebook/notebook.tsx b/packages/widgets/src/notebook/notebook.tsx index 09bad2b5f..e7377b4b9 100644 --- a/packages/widgets/src/notebook/notebook.tsx +++ b/packages/widgets/src/notebook/notebook.tsx @@ -41,21 +41,21 @@ import { IconX, } from "@tabler/icons-react"; import { Color } from "@tiptap/extension-color"; -import Highlight from "@tiptap/extension-highlight"; -import Image from "@tiptap/extension-image"; -import Placeholder from "@tiptap/extension-placeholder"; -import Table from "@tiptap/extension-table"; -import TableCell from "@tiptap/extension-table-cell"; -import TableHeader from "@tiptap/extension-table-header"; -import TableRow from "@tiptap/extension-table-row"; -import TaskItem from "@tiptap/extension-task-item"; -import TaskList from "@tiptap/extension-task-list"; -import TextAlign from "@tiptap/extension-text-align"; -import TextStyle from "@tiptap/extension-text-style"; -import Underline from "@tiptap/extension-underline"; +import { Highlight } from "@tiptap/extension-highlight"; +import { Image } from "@tiptap/extension-image"; +import { Placeholder } from "@tiptap/extension-placeholder"; +import { Table } from "@tiptap/extension-table"; +import { TableCell } from "@tiptap/extension-table-cell"; +import { TableHeader } from "@tiptap/extension-table-header"; +import { TableRow } from "@tiptap/extension-table-row"; +import { TaskItem } from "@tiptap/extension-task-item"; +import { TaskList } from "@tiptap/extension-task-list"; +import { TextAlign } from "@tiptap/extension-text-align"; +import { TextStyle } from "@tiptap/extension-text-style"; import type { Editor } from "@tiptap/react"; -import { BubbleMenu, useEditor } from "@tiptap/react"; -import StarterKit from "@tiptap/starter-kit"; +import { useEditor } from "@tiptap/react"; +import { BubbleMenu } from "@tiptap/react/menus"; +import { StarterKit } from "@tiptap/starter-kit"; import type { Node } from "prosemirror-model"; import { clientApi } from "@homarr/api/client"; @@ -133,7 +133,8 @@ export function Notebook({ options, setOptions, isEditMode, boardId, itemId }: W }; }, }), - StarterKit, + // we use a custom link implementation from mantine + StarterKit.configure({ link: false }), Table.configure({ resizable: true, lastColumnResizable: false, @@ -170,8 +171,9 @@ export function Notebook({ options, setOptions, isEditMode, boardId, itemId }: W TaskList.configure({ itemTypeName: "taskItem" }), TextAlign.configure({ types: ["heading", "paragraph"] }), TextStyle, - Underline, ], + shouldRerenderOnTransaction: true, + immediatelyRender: false, content, onUpdate: ({ editor }) => { setContent(editor.getHTML()); diff --git a/packages/widgets/src/stocks/component.tsx b/packages/widgets/src/stocks/component.tsx index 777cfa0b3..8cfc58b07 100644 --- a/packages/widgets/src/stocks/component.tsx +++ b/packages/widgets/src/stocks/component.tsx @@ -13,12 +13,12 @@ function round(value: number) { return Math.round(value * 100) / 100; } -function calculateChange(valueA: number, valueB: number) { - return valueA - valueB; +function calculateChange(currentPrice: number, previousClose: number) { + return currentPrice - previousClose; } -function calculateChangePercentage(valueA: number, valueB: number) { - return 100 * ((valueA - valueB) / valueA); +function calculateChangePercentage(currentPrice: number, previousClose: number) { + return 100 * ((currentPrice - previousClose) / previousClose); } export default function StockPriceWidget({ options, width, height }: WidgetComponentProps<"stockPrice">) { @@ -26,9 +26,9 @@ export default function StockPriceWidget({ options, width, height }: WidgetCompo const theme = useMantineTheme(); const [{ data }] = clientApi.widget.stockPrice.getPriceHistory.useSuspenseQuery(options); - const stockValuesChange = round(calculateChange(data.priceHistory.at(-1) ?? 0, data.priceHistory[0] ?? 0)); + const stockValuesChange = round(calculateChange(data.priceHistory.at(-1) ?? 0, data.previousClose)); const stockValuesChangePercentage = round( - calculateChangePercentage(data.priceHistory.at(-1) ?? 0, data.priceHistory[0] ?? 0), + calculateChangePercentage(data.priceHistory.at(-1) ?? 0, data.previousClose), ); const stockValuesMin = Math.min(...data.priceHistory); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cd5168351..ca414bfa8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,18 +12,18 @@ overrides: brace-expansion@>=1.0.0 <=1.1.11: '>=4.0.1' esbuild@<=0.24.2: '>=0.27.1' form-data@>=4.0.0 <4.0.4: '>=4.0.5' - hono@<4.6.5: '>=4.10.7' + hono@<4.6.5: '>=4.11.0' linkifyjs@<4.3.2: '>=4.3.2' nanoid@>=4.0.0 <5.0.9: '>=5.1.6' prismjs@<1.30.0: '>=1.30.0' proxmox-api>undici: 7.16.0 - react-is: ^19.2.1 + react-is: ^19.2.3 rollup@>=4.0.0 <4.22.4: '>=4.53.3' sha.js@<=2.4.11: '>=2.4.12' tar-fs@>=3.0.0 <3.0.9: '>=3.1.1' tar-fs@>=2.0.0 <2.1.3: '>=3.1.1' tmp@<=0.2.3: '>=0.2.5' - vite@>=5.0.0 <=5.4.18: '>=7.2.6' + vite@>=5.0.0 <=5.4.18: '>=7.2.7' patchedDependencies: '@types/node-unifi': @@ -53,20 +53,20 @@ importers: specifier: ^12.0.2 version: 12.0.2(semantic-release@25.0.2(typescript@5.9.3)) '@semantic-release/npm': - specifier: ^13.1.2 - version: 13.1.2(semantic-release@25.0.2(typescript@5.9.3)) + specifier: ^13.1.3 + version: 13.1.3(semantic-release@25.0.2(typescript@5.9.3)) '@semantic-release/release-notes-generator': specifier: ^14.1.0 version: 14.1.0(semantic-release@25.0.2(typescript@5.9.3)) '@testcontainers/redis': - specifier: ^11.9.0 - version: 11.9.0 + specifier: ^11.10.0 + version: 11.10.0 '@turbo/gen': specifier: ^2.6.3 - version: 2.6.3(@swc/core@1.15.3)(@types/node@24.10.1)(typescript@5.9.3) + version: 2.6.3(@swc/core@1.15.3)(@types/node@24.10.4)(typescript@5.9.3) '@vitejs/plugin-react': - specifier: ^5.1.1 - version: 5.1.1(vite@7.1.12(@types/node@24.10.1)(sass@1.94.2)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2)) + specifier: ^5.1.2 + version: 5.1.2(vite@7.1.12(@types/node@24.10.4)(sass@1.96.0)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2)) '@vitest/coverage-v8': specifier: ^4.0.15 version: 4.0.15(vitest@4.0.15) @@ -80,8 +80,8 @@ importers: specifier: ^10.1.0 version: 10.1.0 jsdom: - specifier: ^27.2.0 - version: 27.2.0(postcss@8.5.6) + specifier: ^27.3.0 + version: 27.3.0(postcss@8.5.6) json5: specifier: ^2.2.3 version: 2.2.3 @@ -92,8 +92,8 @@ importers: specifier: ^25.0.2 version: 25.0.2(typescript@5.9.3) testcontainers: - specifier: ^11.9.0 - version: 11.9.0 + specifier: ^11.10.0 + version: 11.10.0 turbo: specifier: ^2.6.3 version: 2.6.3 @@ -102,25 +102,25 @@ importers: version: 5.9.3 vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@5.9.3)(vite@7.1.12(@types/node@24.10.1)(sass@1.94.2)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2)) + version: 5.1.4(typescript@5.9.3)(vite@7.1.12(@types/node@24.10.4)(sass@1.96.0)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2)) vitest: specifier: ^4.0.15 - version: 4.0.15(@types/node@24.10.1)(@vitest/ui@4.0.15)(jsdom@27.2.0(postcss@8.5.6))(sass@1.94.2)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2) + version: 4.0.15(@types/node@24.10.4)(@vitest/ui@4.0.15)(jsdom@27.3.0(postcss@8.5.6))(sass@1.96.0)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2) apps/nextjs: dependencies: '@dnd-kit/core': specifier: ^6.3.1 - version: 6.3.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + version: 6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@dnd-kit/modifiers': specifier: ^9.0.0 - version: 9.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1) + version: 9.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3) '@dnd-kit/sortable': specifier: ^10.0.0 - version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1) + version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3) '@dnd-kit/utilities': specifier: ^3.2.2 - version: 3.2.2(react@19.2.1) + version: 3.2.2(react@19.2.3) '@homarr/analytics': specifier: workspace:^0.1.0 version: link:../../packages/analytics @@ -133,9 +133,6 @@ importers: '@homarr/boards': specifier: workspace:^0.1.0 version: link:../../packages/boards - '@homarr/certificates': - specifier: workspace:^0.1.0 - version: link:../../packages/certificates '@homarr/common': specifier: workspace:^0.1.0 version: link:../../packages/common @@ -172,9 +169,6 @@ importers: '@homarr/integrations': specifier: workspace:^0.1.0 version: link:../../packages/integrations - '@homarr/log': - specifier: workspace:^ - version: link:../../packages/log '@homarr/modals': specifier: workspace:^0.1.0 version: link:../../packages/modals @@ -215,53 +209,53 @@ importers: specifier: workspace:^0.1.0 version: link:../../packages/widgets '@mantine/colors-generator': - specifier: ^8.3.9 - version: 8.3.9(chroma-js@3.2.0) + specifier: ^8.3.10 + version: 8.3.10(chroma-js@3.2.0) '@mantine/core': - specifier: ^8.3.9 - version: 8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + specifier: ^8.3.10 + version: 8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@mantine/dropzone': - specifier: ^8.3.9 - version: 8.3.9(@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/hooks@8.3.9(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + specifier: ^8.3.10 + version: 8.3.10(@mantine/core@8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/hooks@8.3.10(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@mantine/hooks': - specifier: ^8.3.9 - version: 8.3.9(react@19.2.1) + specifier: ^8.3.10 + version: 8.3.10(react@19.2.3) '@mantine/modals': - specifier: ^8.3.9 - version: 8.3.9(@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/hooks@8.3.9(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + specifier: ^8.3.10 + version: 8.3.10(@mantine/core@8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/hooks@8.3.10(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@mantine/tiptap': - specifier: ^8.3.9 - version: 8.3.9(@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/hooks@8.3.9(react@19.2.1))(@tiptap/extension-link@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1))(@tiptap/react@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + specifier: ^8.3.10 + version: 8.3.10(@mantine/core@8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/hooks@8.3.10(react@19.2.3))(@tiptap/extension-link@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))(@tiptap/react@3.13.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@million/lint': specifier: 1.0.14 version: 1.0.14(webpack-sources@3.3.3) '@scalar/api-reference-react': specifier: ^0.8.11 - version: 0.8.11(react@19.2.1)(tailwindcss@4.1.18)(typescript@5.9.3) + version: 0.8.11(react@19.2.3)(tailwindcss@4.1.18)(typescript@5.9.3) '@tabler/icons-react': specifier: ^3.35.0 - version: 3.35.0(react@19.2.1) + version: 3.35.0(react@19.2.3) '@tanstack/react-query': specifier: ^5.90.12 - version: 5.90.12(react@19.2.1) + version: 5.90.12(react@19.2.3) '@tanstack/react-query-devtools': specifier: ^5.91.1 - version: 5.91.1(@tanstack/react-query@5.90.12(react@19.2.1))(react@19.2.1) + version: 5.91.1(@tanstack/react-query@5.90.12(react@19.2.3))(react@19.2.3) '@tanstack/react-query-next-experimental': specifier: ^5.91.0 - version: 5.91.0(@tanstack/react-query@5.90.12(react@19.2.1))(next@16.0.7(@babel/core@7.26.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2))(react@19.2.1) + version: 5.91.0(@tanstack/react-query@5.90.12(react@19.2.3))(next@16.0.10(@babel/core@7.26.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.96.0))(react@19.2.3) '@trpc/client': - specifier: ^11.7.2 - version: 11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3) + specifier: ^11.8.0 + version: 11.8.0(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3) '@trpc/next': - specifier: ^11.7.2 - version: 11.7.2(@tanstack/react-query@5.90.12(react@19.2.1))(@trpc/client@11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3))(@trpc/react-query@11.7.2(@tanstack/react-query@5.90.12(react@19.2.1))(@trpc/client@11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.7.2(typescript@5.9.3))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3))(@trpc/server@11.7.2(typescript@5.9.3))(next@16.0.7(@babel/core@7.26.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + specifier: ^11.8.0 + version: 11.8.0(@tanstack/react-query@5.90.12(react@19.2.3))(@trpc/client@11.8.0(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/react-query@11.8.0(@tanstack/react-query@5.90.12(react@19.2.3))(@trpc/client@11.8.0(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.8.0(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(@trpc/server@11.8.0(typescript@5.9.3))(next@16.0.10(@babel/core@7.26.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.96.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) '@trpc/react-query': - specifier: ^11.7.2 - version: 11.7.2(@tanstack/react-query@5.90.12(react@19.2.1))(@trpc/client@11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.7.2(typescript@5.9.3))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + specifier: ^11.8.0 + version: 11.8.0(@tanstack/react-query@5.90.12(react@19.2.3))(@trpc/client@11.8.0(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.8.0(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) '@trpc/server': - specifier: ^11.7.2 - version: 11.7.2(typescript@5.9.3) + specifier: ^11.8.0 + version: 11.8.0(typescript@5.9.3) '@xterm/addon-canvas': specifier: ^0.7.0 version: 0.7.0(@xterm/xterm@5.5.0) @@ -293,17 +287,17 @@ importers: specifier: ^13.0.0 version: 13.0.0 isomorphic-dompurify: - specifier: ^2.33.0 - version: 2.33.0(postcss@8.5.6) + specifier: ^2.34.0 + version: 2.34.0(postcss@8.5.6) jotai: - specifier: ^2.15.2 - version: 2.15.2(@babel/core@7.26.0)(@babel/template@7.27.2)(@types/react@19.2.7)(react@19.2.1) + specifier: ^2.16.0 + version: 2.16.0(@babel/core@7.26.0)(@babel/template@7.27.2)(@types/react@19.2.7)(react@19.2.3) mantine-react-table: specifier: 2.0.0-beta.9 - version: 2.0.0-beta.9(@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/dates@8.3.9(@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/hooks@8.3.9(react@19.2.1))(dayjs@1.11.19)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/hooks@8.3.9(react@19.2.1))(@tabler/icons-react@3.35.0(react@19.2.1))(clsx@2.1.1)(dayjs@1.11.19)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + version: 2.0.0-beta.9(@mantine/core@8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/dates@8.3.10(@mantine/core@8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/hooks@8.3.10(react@19.2.3))(dayjs@1.11.19)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/hooks@8.3.10(react@19.2.3))(@tabler/icons-react@3.35.0(react@19.2.3))(clsx@2.1.1)(dayjs@1.11.19)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) next: - specifier: 16.0.7 - version: 16.0.7(@babel/core@7.26.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2) + specifier: 16.0.10 + version: 16.0.10(@babel/core@7.26.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.96.0) postcss-preset-mantine: specifier: ^1.18.0 version: 1.18.0(postcss@8.5.6) @@ -311,26 +305,26 @@ importers: specifier: ^1.30.0 version: 1.30.0 react: - specifier: 19.2.1 - version: 19.2.1 + specifier: 19.2.3 + version: 19.2.3 react-dom: - specifier: 19.2.1 - version: 19.2.1(react@19.2.1) + specifier: 19.2.3 + version: 19.2.3(react@19.2.3) react-error-boundary: specifier: ^6.0.0 - version: 6.0.0(react@19.2.1) + version: 6.0.0(react@19.2.3) react-simple-code-editor: specifier: ^0.14.1 - version: 0.14.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + version: 0.14.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) sass: - specifier: ^1.94.2 - version: 1.94.2 + specifier: ^1.96.0 + version: 1.96.0 superjson: specifier: 2.2.6 version: 2.2.6 use-deep-compare-effect: specifier: ^1.8.1 - version: 1.8.1(react@19.2.1) + version: 1.8.1(react@19.2.3) zod: specifier: ^4.1.13 version: 4.1.13 @@ -348,8 +342,8 @@ importers: specifier: 3.1.2 version: 3.1.2 '@types/node': - specifier: ^24.10.1 - version: 24.10.1 + specifier: ^24.10.4 + version: 24.10.4 '@types/prismjs': specifier: ^1.26.5 version: 1.26.5 @@ -363,8 +357,8 @@ importers: specifier: ^9.2.1 version: 9.2.1 eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 node-loader: specifier: ^2.1.0 version: 2.1.0(webpack@5.94.0(@swc/core@1.15.3)) @@ -386,6 +380,9 @@ importers: '@homarr/common': specifier: workspace:^0.1.0 version: link:../../packages/common + '@homarr/core': + specifier: workspace:^ + version: link:../../packages/core '@homarr/cron-job-api': specifier: workspace:^0.1.0 version: link:../../packages/cron-job-api @@ -407,9 +404,6 @@ importers: '@homarr/integrations': specifier: workspace:^0.1.0 version: link:../../packages/integrations - '@homarr/log': - specifier: workspace:^ - version: link:../../packages/log '@homarr/ping': specifier: workspace:^0.1.0 version: link:../../packages/ping @@ -454,8 +448,8 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript '@types/node': - specifier: ^24.10.1 - version: 24.10.1 + specifier: ^24.10.4 + version: 24.10.4 dotenv-cli: specifier: ^11.0.0 version: 11.0.0 @@ -463,8 +457,8 @@ importers: specifier: ^0.27.1 version: 0.27.1 eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 prettier: specifier: ^3.7.4 version: 3.7.4 @@ -486,15 +480,15 @@ importers: '@homarr/common': specifier: workspace:^0.1.0 version: link:../../packages/common + '@homarr/core': + specifier: workspace:^ + version: link:../../packages/core '@homarr/db': specifier: workspace:^0.1.0 version: link:../../packages/db '@homarr/definitions': specifier: workspace:^0.1.0 version: link:../../packages/definitions - '@homarr/log': - specifier: workspace:^ - version: link:../../packages/log '@homarr/redis': specifier: workspace:^0.1.0 version: link:../../packages/redis @@ -527,8 +521,8 @@ importers: specifier: ^0.27.1 version: 0.27.1 eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 prettier: specifier: ^3.7.4 version: 3.7.4 @@ -538,12 +532,12 @@ importers: packages/analytics: dependencies: + '@homarr/core': + specifier: workspace:^0.1.0 + version: link:../core '@homarr/db': specifier: workspace:^0.1.0 version: link:../db - '@homarr/log': - specifier: workspace:^0.1.0 - version: link:../log '@homarr/server-settings': specifier: workspace:^0.1.0 version: link:../server-settings @@ -564,8 +558,8 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -575,9 +569,6 @@ importers: '@homarr/auth': specifier: workspace:^0.1.0 version: link:../auth - '@homarr/certificates': - specifier: workspace:^0.1.0 - version: link:../certificates '@homarr/common': specifier: workspace:^0.1.0 version: link:../common @@ -608,9 +599,6 @@ importers: '@homarr/integrations': specifier: workspace:^0.1.0 version: link:../integrations - '@homarr/log': - specifier: workspace:^ - version: link:../log '@homarr/old-import': specifier: workspace:^0.1.0 version: link:../old-import @@ -640,37 +628,37 @@ importers: version: 1.4.0 '@tanstack/react-query': specifier: ^5.90.12 - version: 5.90.12(react@19.2.1) + version: 5.90.12(react@19.2.3) '@trpc/client': - specifier: ^11.7.2 - version: 11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3) + specifier: ^11.8.0 + version: 11.8.0(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3) '@trpc/react-query': - specifier: ^11.7.2 - version: 11.7.2(@tanstack/react-query@5.90.12(react@19.2.1))(@trpc/client@11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.7.2(typescript@5.9.3))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + specifier: ^11.8.0 + version: 11.8.0(@tanstack/react-query@5.90.12(react@19.2.3))(@trpc/client@11.8.0(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.8.0(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) '@trpc/server': - specifier: ^11.7.2 - version: 11.7.2(typescript@5.9.3) + specifier: ^11.8.0 + version: 11.8.0(typescript@5.9.3) '@trpc/tanstack-react-query': - specifier: ^11.7.2 - version: 11.7.2(@tanstack/react-query@5.90.12(react@19.2.1))(@trpc/client@11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.7.2(typescript@5.9.3))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + specifier: ^11.8.0 + version: 11.8.0(@tanstack/react-query@5.90.12(react@19.2.3))(@trpc/client@11.8.0(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.8.0(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) lodash.clonedeep: specifier: ^4.5.0 version: 4.5.0 next: - specifier: 16.0.7 - version: 16.0.7(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2) + specifier: 16.0.10 + version: 16.0.10(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.96.0) react: - specifier: 19.2.1 - version: 19.2.1 + specifier: 19.2.3 + version: 19.2.3 react-dom: - specifier: 19.2.1 - version: 19.2.1(react@19.2.1) + specifier: 19.2.3 + version: 19.2.3(react@19.2.3) superjson: specifier: 2.2.6 version: 2.2.6 trpc-to-openapi: specifier: ^3.1.0 - version: 3.1.0(patch_hash=2ca3c16af0fcca0c736697ad4fe553a14f794524fa9ce0d5c3e8ee4aea76090c)(@trpc/server@11.7.2(typescript@5.9.3))(zod-openapi@5.3.0(zod@4.1.13))(zod@4.1.13) + version: 3.1.0(patch_hash=2ca3c16af0fcca0c736697ad4fe553a14f794524fa9ce0d5c3e8ee4aea76090c)(@trpc/server@11.8.0(typescript@5.9.3))(zod-openapi@5.3.0(zod@4.1.13))(zod@4.1.13) zod: specifier: ^4.1.13 version: 4.1.13 @@ -685,8 +673,8 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 prettier: specifier: ^3.7.4 version: 3.7.4 @@ -702,9 +690,6 @@ importers: '@auth/drizzle-adapter': specifier: ^1.11.1 version: 1.11.1 - '@homarr/certificates': - specifier: workspace:^0.1.0 - version: link:../certificates '@homarr/common': specifier: workspace:^0.1.0 version: link:../common @@ -717,9 +702,6 @@ importers: '@homarr/definitions': specifier: workspace:^0.1.0 version: link:../definitions - '@homarr/log': - specifier: workspace:^0.1.0 - version: link:../log '@homarr/validation': specifier: workspace:^0.1.0 version: link:../validation @@ -730,20 +712,20 @@ importers: specifier: ^0.9.1 version: 0.9.1 ldapts: - specifier: 8.0.12 - version: 8.0.12 + specifier: 8.0.23 + version: 8.0.23 next: - specifier: 16.0.7 - version: 16.0.7(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2) + specifier: 16.0.10 + version: 16.0.10(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.96.0) next-auth: specifier: 5.0.0-beta.30 - version: 5.0.0-beta.30(next@16.0.7(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2))(react@19.2.1) + version: 5.0.0-beta.30(next@16.0.10(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.96.0))(react@19.2.3) react: - specifier: 19.2.1 - version: 19.2.1 + specifier: 19.2.3 + version: 19.2.3 react-dom: - specifier: 19.2.1 - version: 19.2.1(react@19.2.1) + specifier: 19.2.3 + version: 19.2.3(react@19.2.3) zod: specifier: ^4.1.13 version: 4.1.13 @@ -764,8 +746,8 @@ importers: specifier: 0.9.2 version: 0.9.2 eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 prettier: specifier: ^3.7.4 version: 3.7.4 @@ -779,11 +761,11 @@ importers: specifier: workspace:^0.1.0 version: link:../api react: - specifier: 19.2.1 - version: 19.2.1 + specifier: 19.2.3 + version: 19.2.3 react-dom: - specifier: 19.2.1 - version: 19.2.1(react@19.2.1) + specifier: 19.2.3 + version: 19.2.3(react@19.2.3) devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -795,36 +777,8 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.39.1 - version: 9.39.1 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - - packages/certificates: - dependencies: - '@homarr/common': - specifier: workspace:^0.1.0 - version: link:../common - '@homarr/db': - specifier: workspace:^0.1.0 - version: link:../db - undici: - specifier: 7.16.0 - version: 7.16.0 - devDependencies: - '@homarr/eslint-config': - specifier: workspace:^0.2.0 - version: link:../../tooling/eslint - '@homarr/prettier-config': - specifier: workspace:^0.1.0 - version: link:../../tooling/prettier - '@homarr/tsconfig': - specifier: workspace:^0.1.0 - version: link:../../tooling/typescript - eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -863,8 +817,8 @@ importers: specifier: ^0.27.1 version: 0.27.1 eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -874,30 +828,24 @@ importers: '@homarr/core': specifier: workspace:^0.1.0 version: link:../core - '@homarr/log': - specifier: workspace:^0.1.0 - version: link:../log '@paralleldrive/cuid2': specifier: ^3.1.0 version: 3.1.0 dayjs: specifier: ^1.11.19 version: 1.11.19 - dns-caching: - specifier: ^0.2.7 - version: 0.2.7 next: - specifier: 16.0.7 - version: 16.0.7(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2) + specifier: 16.0.10 + version: 16.0.10(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.96.0) octokit: specifier: ^5.0.5 version: 5.0.5 react: - specifier: 19.2.1 - version: 19.2.1 + specifier: 19.2.3 + version: 19.2.3 react-dom: - specifier: 19.2.1 - version: 19.2.1(react@19.2.1) + specifier: 19.2.3 + version: 19.2.3(react@19.2.3) undici: specifier: 7.16.0 version: 7.16.0 @@ -918,8 +866,8 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -929,9 +877,30 @@ importers: '@t3-oss/env-nextjs': specifier: ^0.13.8 version: 0.13.8(arktype@2.1.20)(typescript@5.9.3)(zod@4.1.13) + better-sqlite3: + specifier: ^12.5.0 + version: 12.5.0 + dns-caching: + specifier: ^0.2.9 + version: 0.2.9 + drizzle-orm: + specifier: ^0.45.1 + version: 0.45.1(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(better-sqlite3@12.5.0)(gel@2.0.0)(mysql2@3.15.3)(pg@8.16.3) ioredis: specifier: 5.8.2 version: 5.8.2 + mysql2: + specifier: 3.15.3 + version: 3.15.3 + pg: + specifier: ^8.16.3 + version: 8.16.3 + superjson: + specifier: 2.2.6 + version: 2.2.6 + winston: + specifier: 3.19.0 + version: 3.19.0 zod: specifier: ^4.1.13 version: 4.1.13 @@ -945,9 +914,15 @@ importers: '@homarr/tsconfig': specifier: workspace:^0.1.0 version: link:../../tooling/typescript + '@types/better-sqlite3': + specifier: 7.6.13 + version: 7.6.13 + '@types/pg': + specifier: ^8.16.0 + version: 8.16.0 eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -963,27 +938,24 @@ importers: '@homarr/cron-jobs': specifier: workspace:^0.1.0 version: link:../cron-jobs - '@homarr/log': - specifier: workspace:^0.1.0 - version: link:../log '@tanstack/react-query': specifier: ^5.90.12 - version: 5.90.12(react@19.2.1) + version: 5.90.12(react@19.2.3) '@trpc/client': - specifier: ^11.7.2 - version: 11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3) + specifier: ^11.8.0 + version: 11.8.0(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3) '@trpc/server': - specifier: ^11.7.2 - version: 11.7.2(typescript@5.9.3) + specifier: ^11.8.0 + version: 11.8.0(typescript@5.9.3) '@trpc/tanstack-react-query': - specifier: ^11.7.2 - version: 11.7.2(@tanstack/react-query@5.90.12(react@19.2.1))(@trpc/client@11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.7.2(typescript@5.9.3))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + specifier: ^11.8.0 + version: 11.8.0(@tanstack/react-query@5.90.12(react@19.2.3))(@trpc/client@11.8.0(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.8.0(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) node-cron: specifier: ^4.2.1 version: 4.2.1 react: - specifier: 19.2.1 - version: 19.2.1 + specifier: 19.2.3 + version: 19.2.3 zod: specifier: ^4.1.13 version: 4.1.13 @@ -1004,8 +976,8 @@ importers: specifier: 19.2.7 version: 19.2.7 eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -1026,8 +998,8 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -1043,6 +1015,9 @@ importers: '@homarr/common': specifier: workspace:^0.1.0 version: link:../common + '@homarr/core': + specifier: workspace:^0.1.0 + version: link:../core '@homarr/cron-job-status': specifier: workspace:^0.1.0 version: link:../cron-job-status @@ -1061,9 +1036,6 @@ importers: '@homarr/integrations': specifier: workspace:^0.1.0 version: link:../integrations - '@homarr/log': - specifier: workspace:^0.1.0 - version: link:../log '@homarr/ping': specifier: workspace:^0.1.0 version: link:../ping @@ -1096,8 +1068,8 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -1107,6 +1079,9 @@ importers: '@homarr/common': specifier: workspace:^0.1.0 version: link:../common + '@homarr/core': + specifier: workspace:^ + version: link:../core '@homarr/db': specifier: workspace:^0.1.0 version: link:../db @@ -1127,8 +1102,8 @@ importers: specifier: ^3.0.11 version: 3.0.11 eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -1147,24 +1122,21 @@ importers: '@homarr/definitions': specifier: workspace:^0.1.0 version: link:../definitions - '@homarr/log': - specifier: workspace:^0.1.0 - version: link:../log '@homarr/server-settings': specifier: workspace:^0.1.0 version: link:../server-settings '@mantine/core': - specifier: ^8.3.9 - version: 8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + specifier: ^8.3.10 + version: 8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@paralleldrive/cuid2': specifier: ^3.1.0 version: 3.1.0 '@testcontainers/mysql': - specifier: ^11.9.0 - version: 11.9.0 + specifier: ^11.10.0 + version: 11.10.0 '@testcontainers/postgresql': - specifier: ^11.9.0 - version: 11.9.0 + specifier: ^11.10.0 + version: 11.10.0 better-sqlite3: specifier: ^12.5.0 version: 12.5.0 @@ -1175,11 +1147,11 @@ importers: specifier: ^0.31.8 version: 0.31.8 drizzle-orm: - specifier: ^0.45.0 - version: 0.45.0(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.15.6)(better-sqlite3@12.5.0)(gel@2.0.0)(mysql2@3.15.3)(pg@8.16.3) + specifier: ^0.45.1 + version: 0.45.1(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(better-sqlite3@12.5.0)(gel@2.0.0)(mysql2@3.15.3)(pg@8.16.3) drizzle-zod: specifier: ^0.8.3 - version: 0.8.3(drizzle-orm@0.45.0(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.15.6)(better-sqlite3@12.5.0)(gel@2.0.0)(mysql2@3.15.3)(pg@8.16.3))(zod@4.1.13) + version: 0.8.3(drizzle-orm@0.45.1(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(better-sqlite3@12.5.0)(gel@2.0.0)(mysql2@3.15.3)(pg@8.16.3))(zod@4.1.13) mysql2: specifier: 3.15.3 version: 3.15.3 @@ -1203,8 +1175,8 @@ importers: specifier: 7.6.13 version: 7.6.13 '@types/pg': - specifier: ^8.15.6 - version: 8.15.6 + specifier: ^8.16.0 + version: 8.16.0 dotenv-cli: specifier: ^11.0.0 version: 11.0.0 @@ -1212,8 +1184,8 @@ importers: specifier: ^0.27.1 version: 0.27.1 eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 prettier: specifier: ^3.7.4 version: 3.7.4 @@ -1230,8 +1202,8 @@ importers: specifier: workspace:^0.1.0 version: link:../common fast-xml-parser: - specifier: ^5.3.2 - version: 5.3.2 + specifier: ^5.3.3 + version: 5.3.3 zod: specifier: ^4.1.13 version: 4.1.13 @@ -1246,8 +1218,8 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 tsx: specifier: 4.20.4 version: 4.20.4 @@ -1280,8 +1252,8 @@ importers: specifier: ^3.3.47 version: 3.3.47 eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -1298,11 +1270,11 @@ importers: specifier: workspace:^0.1.0 version: link:../validation '@mantine/form': - specifier: ^8.3.9 - version: 8.3.9(react@19.2.1) + specifier: ^8.3.10 + version: 8.3.10(react@19.2.3) mantine-form-zod-resolver: specifier: ^1.3.0 - version: 1.3.0(@mantine/form@8.3.9(react@19.2.1))(zod@4.1.13) + version: 1.3.0(@mantine/form@8.3.10(react@19.2.3))(zod@4.1.13) zod: specifier: ^4.1.13 version: 4.1.13 @@ -1317,8 +1289,8 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -1350,11 +1322,11 @@ importers: specifier: workspace:^0.1.0 version: link:../validation '@mantine/core': - specifier: ^8.3.9 - version: 8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + specifier: ^8.3.10 + version: 8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: - specifier: 19.2.1 - version: 19.2.1 + specifier: 19.2.3 + version: 19.2.3 zod: specifier: ^4.1.13 version: 4.1.13 @@ -1369,8 +1341,8 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -1380,12 +1352,12 @@ importers: '@homarr/common': specifier: workspace:^0.1.0 version: link:../common + '@homarr/core': + specifier: workspace:^0.1.0 + version: link:../core '@homarr/db': specifier: workspace:^0.1.0 version: link:../db - '@homarr/log': - specifier: workspace:^0.1.0 - version: link:../log devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -1397,23 +1369,20 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 packages/image-proxy: dependencies: - '@homarr/certificates': - specifier: workspace:^0.1.0 - version: link:../certificates '@homarr/common': specifier: workspace:^0.1.0 version: link:../common - '@homarr/log': + '@homarr/core': specifier: workspace:^0.1.0 - version: link:../log + version: link:../core '@homarr/redis': specifier: workspace:^0.1.0 version: link:../redis @@ -1434,8 +1403,8 @@ importers: specifier: 6.0.0 version: 6.0.0 eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -1454,12 +1423,12 @@ importers: '@gitbeaker/rest': specifier: ^43.8.0 version: 43.8.0 - '@homarr/certificates': - specifier: workspace:^0.1.0 - version: link:../certificates '@homarr/common': specifier: workspace:^0.1.0 version: link:../common + '@homarr/core': + specifier: workspace:^0.1.0 + version: link:../core '@homarr/db': specifier: workspace:^0.1.0 version: link:../db @@ -1469,9 +1438,6 @@ importers: '@homarr/image-proxy': specifier: workspace:^0.1.0 version: link:../image-proxy - '@homarr/log': - specifier: workspace:^0.1.0 - version: link:../log '@homarr/node-unifi': specifier: ^2.6.0 version: 2.6.0(undici@7.16.0) @@ -1534,39 +1500,8 @@ importers: specifier: ^0.4.14 version: 0.4.14 eslint: - specifier: ^9.39.1 - version: 9.39.1 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - - packages/log: - dependencies: - '@homarr/core': - specifier: workspace:^0.1.0 - version: link:../core - superjson: - specifier: 2.2.6 - version: 2.2.6 - winston: - specifier: 3.18.3 - version: 3.18.3 - zod: - specifier: ^4.1.13 - version: 4.1.13 - devDependencies: - '@homarr/eslint-config': - specifier: workspace:^0.2.0 - version: link:../../tooling/eslint - '@homarr/prettier-config': - specifier: workspace:^0.1.0 - version: link:../../tooling/prettier - '@homarr/tsconfig': - specifier: workspace:^0.1.0 - version: link:../../tooling/typescript - eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -1580,14 +1515,14 @@ importers: specifier: workspace:^0.1.0 version: link:../ui '@mantine/core': - specifier: ^8.3.9 - version: 8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + specifier: ^8.3.10 + version: 8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@mantine/hooks': - specifier: ^8.3.9 - version: 8.3.9(react@19.2.1) + specifier: ^8.3.10 + version: 8.3.10(react@19.2.3) react: - specifier: 19.2.1 - version: 19.2.1 + specifier: 19.2.3 + version: 19.2.3 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -1599,8 +1534,8 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -1641,23 +1576,23 @@ importers: specifier: workspace:^0.1.0 version: link:../validation '@mantine/core': - specifier: ^8.3.9 - version: 8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + specifier: ^8.3.10 + version: 8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tabler/icons-react': specifier: ^3.35.0 - version: 3.35.0(react@19.2.1) + version: 3.35.0(react@19.2.3) dayjs: specifier: ^1.11.19 version: 1.11.19 next: - specifier: 16.0.7 - version: 16.0.7(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2) + specifier: 16.0.10 + version: 16.0.10(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.96.0) react: - specifier: 19.2.1 - version: 19.2.1 + specifier: 19.2.3 + version: 19.2.3 react-dom: - specifier: 19.2.1 - version: 19.2.1(react@19.2.1) + specifier: 19.2.3 + version: 19.2.3(react@19.2.3) zod: specifier: ^4.1.13 version: 4.1.13 @@ -1672,8 +1607,8 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -1684,11 +1619,11 @@ importers: specifier: workspace:^0.1.0 version: link:../ui '@mantine/notifications': - specifier: ^8.3.9 - version: 8.3.9(@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/hooks@8.3.9(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + specifier: ^8.3.10 + version: 8.3.10(@mantine/core@8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/hooks@8.3.10(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tabler/icons-react': specifier: ^3.35.0 - version: 3.35.0(react@19.2.1) + version: 3.35.0(react@19.2.3) devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -1700,8 +1635,8 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -1711,6 +1646,9 @@ importers: '@homarr/common': specifier: workspace:^0.1.0 version: link:../common + '@homarr/core': + specifier: workspace:^0.1.0 + version: link:../core '@homarr/db': specifier: workspace:^0.1.0 version: link:../db @@ -1720,9 +1658,6 @@ importers: '@homarr/form': specifier: workspace:^0.1.0 version: link:../form - '@homarr/log': - specifier: workspace:^0.1.0 - version: link:../log '@homarr/modals': specifier: workspace:^0.1.0 version: link:../modals @@ -1742,23 +1677,23 @@ importers: specifier: workspace:^0.1.0 version: link:../validation '@mantine/core': - specifier: ^8.3.9 - version: 8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + specifier: ^8.3.10 + version: 8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@mantine/hooks': - specifier: ^8.3.9 - version: 8.3.9(react@19.2.1) + specifier: ^8.3.10 + version: 8.3.10(react@19.2.3) adm-zip: specifier: 0.5.16 version: 0.5.16 next: - specifier: 16.0.7 - version: 16.0.7(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2) + specifier: 16.0.10 + version: 16.0.10(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.96.0) react: - specifier: 19.2.1 - version: 19.2.1 + specifier: 19.2.3 + version: 19.2.3 react-dom: - specifier: 19.2.1 - version: 19.2.1(react@19.2.1) + specifier: 19.2.3 + version: 19.2.3(react@19.2.3) superjson: specifier: 2.2.6 version: 2.2.6 @@ -1782,8 +1717,8 @@ importers: specifier: 0.5.7 version: 0.5.7 eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -1807,23 +1742,20 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 packages/ping: dependencies: - '@homarr/certificates': - specifier: workspace:^0.1.0 - version: link:../certificates '@homarr/common': specifier: workspace:^0.1.0 version: link:../common - '@homarr/log': + '@homarr/core': specifier: workspace:^0.1.0 - version: link:../log + version: link:../core devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -1835,8 +1767,8 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -1855,9 +1787,6 @@ importers: '@homarr/definitions': specifier: workspace:^ version: link:../definitions - '@homarr/log': - specifier: workspace:^ - version: link:../log ioredis: specifier: 5.8.2 version: 5.8.2 @@ -1875,8 +1804,8 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -1889,6 +1818,9 @@ importers: '@homarr/common': specifier: workspace:^0.1.0 version: link:../common + '@homarr/core': + specifier: workspace:^0.1.0 + version: link:../core '@homarr/db': specifier: workspace:^0.1.0 version: link:../db @@ -1901,9 +1833,6 @@ importers: '@homarr/integrations': specifier: workspace:^0.1.0 version: link:../integrations - '@homarr/log': - specifier: workspace:^0.1.0 - version: link:../log '@homarr/redis': specifier: workspace:^0.1.0 version: link:../redis @@ -1933,8 +1862,8 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -1958,8 +1887,8 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -1976,17 +1905,17 @@ importers: specifier: workspace:^0.1.0 version: link:../server-settings '@mantine/dates': - specifier: ^8.3.9 - version: 8.3.9(@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/hooks@8.3.9(react@19.2.1))(dayjs@1.11.19)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + specifier: ^8.3.10 + version: 8.3.10(@mantine/core@8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/hooks@8.3.10(react@19.2.3))(dayjs@1.11.19)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) next: - specifier: 16.0.7 - version: 16.0.7(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2) + specifier: 16.0.10 + version: 16.0.10(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.96.0) react: - specifier: 19.2.1 - version: 19.2.1 + specifier: 19.2.3 + version: 19.2.3 react-dom: - specifier: 19.2.1 - version: 19.2.1(react@19.2.1) + specifier: 19.2.3 + version: 19.2.3(react@19.2.3) devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -1998,8 +1927,8 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -2037,32 +1966,32 @@ importers: specifier: workspace:^0.1.0 version: link:../ui '@mantine/core': - specifier: ^8.3.9 - version: 8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + specifier: ^8.3.10 + version: 8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@mantine/hooks': - specifier: ^8.3.9 - version: 8.3.9(react@19.2.1) + specifier: ^8.3.10 + version: 8.3.10(react@19.2.3) '@mantine/spotlight': - specifier: ^8.3.9 - version: 8.3.9(@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/hooks@8.3.9(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + specifier: ^8.3.10 + version: 8.3.10(@mantine/core@8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/hooks@8.3.10(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tabler/icons-react': specifier: ^3.35.0 - version: 3.35.0(react@19.2.1) + version: 3.35.0(react@19.2.3) jotai: - specifier: ^2.15.2 - version: 2.15.2(@babel/core@7.28.5)(@babel/template@7.27.2)(@types/react@19.2.7)(react@19.2.1) + specifier: ^2.16.0 + version: 2.16.0(@babel/core@7.28.5)(@babel/template@7.27.2)(@types/react@19.2.7)(react@19.2.3) next: - specifier: 16.0.7 - version: 16.0.7(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2) + specifier: 16.0.10 + version: 16.0.10(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.96.0) react: - specifier: 19.2.1 - version: 19.2.1 + specifier: 19.2.3 + version: 19.2.3 react-dom: - specifier: 19.2.1 - version: 19.2.1(react@19.2.1) + specifier: 19.2.3 + version: 19.2.3(react@19.2.3) use-deep-compare-effect: specifier: ^1.8.1 - version: 1.8.1(react@19.2.1) + version: 1.8.1(react@19.2.3) devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -2074,8 +2003,8 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -2096,19 +2025,19 @@ importers: version: 4.3.1 mantine-react-table: specifier: 2.0.0-beta.9 - version: 2.0.0-beta.9(@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/dates@8.3.9(@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/hooks@8.3.9(react@19.2.1))(dayjs@1.11.19)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/hooks@8.3.9(react@19.2.1))(@tabler/icons-react@3.35.0(react@19.2.1))(clsx@2.1.1)(dayjs@1.11.19)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + version: 2.0.0-beta.9(@mantine/core@8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/dates@8.3.10(@mantine/core@8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/hooks@8.3.10(react@19.2.3))(dayjs@1.11.19)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/hooks@8.3.10(react@19.2.3))(@tabler/icons-react@3.35.0(react@19.2.3))(clsx@2.1.1)(dayjs@1.11.19)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) next: - specifier: 16.0.7 - version: 16.0.7(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2) + specifier: 16.0.10 + version: 16.0.10(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.96.0) next-intl: - specifier: 4.5.8 - version: 4.5.8(next@16.0.7(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2))(react@19.2.1)(typescript@5.9.3) + specifier: 4.6.0 + version: 4.6.0(next@16.0.10(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.96.0))(react@19.2.3)(typescript@5.9.3) react: - specifier: 19.2.1 - version: 19.2.1 + specifier: 19.2.3 + version: 19.2.3 react-dom: - specifier: 19.2.1 - version: 19.2.1(react@19.2.1) + specifier: 19.2.3 + version: 19.2.3(react@19.2.3) devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -2120,8 +2049,8 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -2134,9 +2063,6 @@ importers: '@homarr/definitions': specifier: workspace:^0.1.0 version: link:../definitions - '@homarr/log': - specifier: workspace:^0.1.0 - version: link:../log '@homarr/translation': specifier: workspace:^0.1.0 version: link:../translation @@ -2144,29 +2070,29 @@ importers: specifier: workspace:^0.1.0 version: link:../validation '@mantine/core': - specifier: ^8.3.9 - version: 8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + specifier: ^8.3.10 + version: 8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@mantine/dates': - specifier: ^8.3.9 - version: 8.3.9(@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/hooks@8.3.9(react@19.2.1))(dayjs@1.11.19)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + specifier: ^8.3.10 + version: 8.3.10(@mantine/core@8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/hooks@8.3.10(react@19.2.3))(dayjs@1.11.19)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@mantine/hooks': - specifier: ^8.3.9 - version: 8.3.9(react@19.2.1) + specifier: ^8.3.10 + version: 8.3.10(react@19.2.3) '@tabler/icons-react': specifier: ^3.35.0 - version: 3.35.0(react@19.2.1) + version: 3.35.0(react@19.2.3) mantine-react-table: specifier: 2.0.0-beta.9 - version: 2.0.0-beta.9(@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/dates@8.3.9(@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/hooks@8.3.9(react@19.2.1))(dayjs@1.11.19)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/hooks@8.3.9(react@19.2.1))(@tabler/icons-react@3.35.0(react@19.2.1))(clsx@2.1.1)(dayjs@1.11.19)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + version: 2.0.0-beta.9(@mantine/core@8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/dates@8.3.10(@mantine/core@8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/hooks@8.3.10(react@19.2.3))(dayjs@1.11.19)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/hooks@8.3.10(react@19.2.3))(@tabler/icons-react@3.35.0(react@19.2.3))(clsx@2.1.1)(dayjs@1.11.19)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) next: - specifier: 16.0.7 - version: 16.0.7(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2) + specifier: 16.0.10 + version: 16.0.10(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.96.0) react: - specifier: 19.2.1 - version: 19.2.1 + specifier: 19.2.3 + version: 19.2.3 react-dom: - specifier: 19.2.1 - version: 19.2.1(react@19.2.1) + specifier: 19.2.3 + version: 19.2.3(react@19.2.3) svgson: specifier: ^5.3.1 version: 5.3.1 @@ -2184,8 +2110,8 @@ importers: specifier: ^1.0.5 version: 1.0.5 eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -2215,8 +2141,8 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -2225,10 +2151,10 @@ importers: dependencies: '@dnd-kit/core': specifier: ^6.3.1 - version: 6.3.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + version: 6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@dnd-kit/sortable': specifier: ^10.0.0 - version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1) + version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3) '@homarr/api': specifier: workspace:^0.1.0 version: link:../api @@ -2241,6 +2167,9 @@ importers: '@homarr/common': specifier: workspace:^0.1.0 version: link:../common + '@homarr/core': + specifier: workspace:^0.1.0 + version: link:../core '@homarr/db': specifier: workspace:^0.1.0 version: link:../db @@ -2259,9 +2188,6 @@ importers: '@homarr/integrations': specifier: workspace:^0.1.0 version: link:../integrations - '@homarr/log': - specifier: workspace:^0.1.0 - version: link:../log '@homarr/modals': specifier: workspace:^0.1.0 version: link:../modals @@ -2293,65 +2219,65 @@ importers: specifier: workspace:^0.1.0 version: link:../validation '@mantine/charts': - specifier: ^8.3.9 - version: 8.3.9(@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/hooks@8.3.9(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(recharts@2.15.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1)) + specifier: ^8.3.10 + version: 8.3.10(@mantine/core@8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/hooks@8.3.10(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(recharts@2.15.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) '@mantine/core': - specifier: ^8.3.9 - version: 8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + specifier: ^8.3.10 + version: 8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@mantine/hooks': - specifier: ^8.3.9 - version: 8.3.9(react@19.2.1) + specifier: ^8.3.10 + version: 8.3.10(react@19.2.3) '@tabler/icons-react': specifier: ^3.35.0 - version: 3.35.0(react@19.2.1) + version: 3.35.0(react@19.2.3) '@tiptap/extension-color': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/extension-text-style@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))) + specifier: 3.13.0 + version: 3.13.0(@tiptap/extension-text-style@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))) '@tiptap/extension-highlight': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) + specifier: 3.13.0 + version: 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0)) '@tiptap/extension-image': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) + specifier: 3.13.0 + version: 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0)) '@tiptap/extension-link': - specifier: ^2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) + specifier: ^3.13.0 + version: 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0) '@tiptap/extension-placeholder': - specifier: ^2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) + specifier: ^3.13.0 + version: 3.13.0(@tiptap/extensions@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)) '@tiptap/extension-table': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) + specifier: 3.13.0 + version: 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0) '@tiptap/extension-table-cell': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) + specifier: 3.13.0 + version: 3.13.0(@tiptap/extension-table@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)) '@tiptap/extension-table-header': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) + specifier: 3.13.0 + version: 3.13.0(@tiptap/extension-table@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)) '@tiptap/extension-table-row': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) + specifier: 3.13.0 + version: 3.13.0(@tiptap/extension-table@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)) '@tiptap/extension-task-item': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) + specifier: 3.13.0 + version: 3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)) '@tiptap/extension-task-list': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) + specifier: 3.13.0 + version: 3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)) '@tiptap/extension-text-align': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) + specifier: 3.13.0 + version: 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0)) '@tiptap/extension-text-style': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) + specifier: 3.13.0 + version: 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0)) '@tiptap/extension-underline': - specifier: 2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) + specifier: 3.13.0 + version: 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0)) '@tiptap/react': - specifier: ^2.27.1 - version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + specifier: ^3.13.0 + version: 3.13.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tiptap/starter-kit': - specifier: ^2.27.1 - version: 2.27.1 + specifier: ^3.13.0 + version: 3.13.0 clsx: specifier: ^2.1.1 version: 2.1.1 @@ -2360,25 +2286,25 @@ importers: version: 1.11.19 mantine-form-zod-resolver: specifier: ^1.3.0 - version: 1.3.0(@mantine/form@8.3.9(react@19.2.1))(zod@4.1.13) + version: 1.3.0(@mantine/form@8.3.10(react@19.2.3))(zod@4.1.13) mantine-react-table: specifier: 2.0.0-beta.9 - version: 2.0.0-beta.9(@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/dates@8.3.9(@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/hooks@8.3.9(react@19.2.1))(dayjs@1.11.19)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/hooks@8.3.9(react@19.2.1))(@tabler/icons-react@3.35.0(react@19.2.1))(clsx@2.1.1)(dayjs@1.11.19)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + version: 2.0.0-beta.9(@mantine/core@8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/dates@8.3.10(@mantine/core@8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/hooks@8.3.10(react@19.2.3))(dayjs@1.11.19)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/hooks@8.3.10(react@19.2.3))(@tabler/icons-react@3.35.0(react@19.2.3))(clsx@2.1.1)(dayjs@1.11.19)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) next: - specifier: 16.0.7 - version: 16.0.7(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2) + specifier: 16.0.10 + version: 16.0.10(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.96.0) react: - specifier: 19.2.1 - version: 19.2.1 + specifier: 19.2.3 + version: 19.2.3 react-dom: - specifier: 19.2.1 - version: 19.2.1(react@19.2.1) + specifier: 19.2.3 + version: 19.2.3(react@19.2.3) react-markdown: specifier: ^10.1.0 - version: 10.1.0(@types/react@19.2.7)(react@19.2.1) + version: 10.1.0(@types/react@19.2.7)(react@19.2.3) recharts: specifier: ^2.15.4 - version: 2.15.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + version: 2.15.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3) video.js: specifier: ^8.23.4 version: 8.23.4 @@ -2399,8 +2325,8 @@ importers: specifier: ^7.3.58 version: 7.3.58 eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -2408,29 +2334,29 @@ importers: tooling/eslint: dependencies: '@next/eslint-plugin-next': - specifier: 16.0.7 - version: 16.0.7 + specifier: 16.0.10 + version: 16.0.10 eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@9.39.1) + version: 10.1.8(eslint@9.39.2) eslint-config-turbo: specifier: ^2.6.3 - version: 2.6.3(eslint@9.39.1)(turbo@2.6.3) + version: 2.6.3(eslint@9.39.2)(turbo@2.6.3) eslint-plugin-import: specifier: ^2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1) + version: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2) eslint-plugin-jsx-a11y: specifier: ^6.10.2 - version: 6.10.2(eslint@9.39.1) + version: 6.10.2(eslint@9.39.2) eslint-plugin-react: specifier: ^7.37.5 - version: 7.37.5(eslint@9.39.1) + version: 7.37.5(eslint@9.39.2) eslint-plugin-react-hooks: specifier: ^6.1.1 - version: 6.1.1(eslint@9.39.1) + version: 6.1.1(eslint@9.39.2) typescript-eslint: - specifier: ^8.48.1 - version: 8.48.1(eslint@9.39.1)(typescript@5.9.3) + specifier: ^8.49.0 + version: 8.49.0(eslint@9.39.2)(typescript@5.9.3) devDependencies: '@homarr/prettier-config': specifier: workspace:^0.1.0 @@ -2439,8 +2365,8 @@ importers: specifier: workspace:^0.1.0 version: link:../typescript eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -2470,20 +2396,20 @@ importers: packages: - '@acemir/cssom@0.9.23': - resolution: {integrity: sha512-2kJ1HxBKzPLbmhZpxBiTZggjtgCwKg1ma5RHShxvd6zgqhDEdEkzpiwe7jLkI2p2BrZvFCXIihdoMkl1H39VnA==} + '@acemir/cssom@0.9.28': + resolution: {integrity: sha512-LuS6IVEivI75vKN8S04qRD+YySP0RmU/cV8UNukhQZvprxF+76Z43TNo/a08eCodaGhT1Us8etqS1ZRY9/Or0A==} - '@actions/core@1.11.1': - resolution: {integrity: sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==} + '@actions/core@2.0.1': + resolution: {integrity: sha512-oBfqT3GwkvLlo1fjvhQLQxuwZCGTarTE5OuZ2Wg10hvhBj7LRIlF611WT4aZS6fDhO5ZKlY7lCAZTlpmyaHaeg==} - '@actions/exec@1.1.1': - resolution: {integrity: sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==} + '@actions/exec@2.0.0': + resolution: {integrity: sha512-k8ngrX2voJ/RIN6r9xB82NVqKpnMRtxDoiO+g3olkIUpQNqjArXrCQceduQZCQj3P3xm32pChRLqRrtXTlqhIw==} - '@actions/http-client@2.2.3': - resolution: {integrity: sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==} + '@actions/http-client@3.0.0': + resolution: {integrity: sha512-1s3tXAfVMSz9a4ZEBkXXRQD4QhY3+GAsWSbaYpeknPOKEeyRiU3lH+bHiLMZdo2x/fIeQ/hscL1wCkDLVM2DZQ==} - '@actions/io@1.1.3': - resolution: {integrity: sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==} + '@actions/io@2.0.0': + resolution: {integrity: sha512-Jv33IN09XLO+0HS79aaODsvIRyduiF7NY/F6LYeK5oeUmrsz7aFdRphQjFoESF4jS7lMauDOttKALcpapVDIAg==} '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} @@ -2499,11 +2425,11 @@ packages: '@ark/util@0.46.0': resolution: {integrity: sha512-JPy/NGWn/lvf1WmGCPw2VGpBg5utZraE84I7wli18EDF3p3zc/e9WolT35tINeZO3l7C77SjqRJeAUoT0CvMRg==} - '@asamuzakjp/css-color@4.0.4': - resolution: {integrity: sha512-cKjSKvWGmAziQWbCouOsFwb14mp1betm8Y7Fn+yglDMUUu3r9DCbJ9iJbeFDenLMqFbIMC0pQP8K+B8LAxX3OQ==} + '@asamuzakjp/css-color@4.1.0': + resolution: {integrity: sha512-9xiBAtLn4aNsa4mDnpovJvBn72tNEIACyvlqaNJ+ADemR+yeMJWnBudOi2qGDviJa7SwcDOU/TRh5dnET7qk0w==} - '@asamuzakjp/dom-selector@6.7.4': - resolution: {integrity: sha512-buQDjkm+wDPXd6c13534URWZqbz0RP5PAhXZ+LIoa5LgwInT9HVJvGIJivg75vi8I13CxDGdTnz+aY5YUJlIAA==} + '@asamuzakjp/dom-selector@6.7.6': + resolution: {integrity: sha512-hBaJER6A9MpdG3WgdlOolHmbOYvSk46y7IQN/1+iqiCuUu6iWdQrs9DGKF8ocqsEqWujWf/V7b7vaDgiUmIvUg==} '@asamuzakjp/nwsapi@2.3.9': resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} @@ -3256,8 +3182,8 @@ packages: resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.39.1': - resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==} + '@eslint/js@9.39.2': + resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/object-schema@2.1.7': @@ -3380,7 +3306,7 @@ packages: resolution: {integrity: sha512-kz323qIQkNQElEGroo/E9MKPDuIR5pkuk/XEWd50K+cSEKdmdiYx0PKWUdaNY2ecJYngtF+njDMsMKplL6zfEg==} engines: {node: '>=18.14.1'} peerDependencies: - hono: '>=4.10.7' + hono: '>=4.11.0' '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} @@ -3685,88 +3611,88 @@ packages: '@libsql/core@0.14.0': resolution: {integrity: sha512-nhbuXf7GP3PSZgdCY2Ecj8vz187ptHlZQ0VRc751oB2C1W8jQUXKKklvt7t1LJiUTQBVJuadF628eUk+3cRi4Q==} - '@mantine/charts@8.3.9': - resolution: {integrity: sha512-BiwDroAjS+0KwMBR8c1GHn2gSrkAZa+4iUisVFUeqsJ8FCxf4RtzdckAe/SG2q0FQYaQ/o4bCSmrbiEGaY+Skw==} + '@mantine/charts@8.3.10': + resolution: {integrity: sha512-/JbuxY7qzrxrZR7ZjKj9dD8OXq03nAIClqJ+fD5ezF8J1cVYH9nx0IaIu8RPpaT4UwRdxz+TH/EutQ0LdeOz8w==} peerDependencies: - '@mantine/core': 8.3.9 - '@mantine/hooks': 8.3.9 + '@mantine/core': 8.3.10 + '@mantine/hooks': 8.3.10 react: ^18.x || ^19.x react-dom: ^18.x || ^19.x recharts: '>=2.13.3' - '@mantine/colors-generator@8.3.9': - resolution: {integrity: sha512-WWBbJj4t0KHyTyeBt/3E3TUwuZETIB7MYG0zNU+pXFMTmRKACoYxBqEPZNMATKp5z7JzCg4a4NLecGEJk56mlg==} + '@mantine/colors-generator@8.3.10': + resolution: {integrity: sha512-wjTFe4jfnjnPPv/B7mu4tN3iBQiqynQ8sMf8R8JV5NeKZyaG+1EptqplyZrrLfqYm76GF7+cZbj2wTM64v8/ig==} peerDependencies: chroma-js: '>=2.4.2' - '@mantine/core@8.3.9': - resolution: {integrity: sha512-ivj0Crn5N521cI2eWZBsBGckg0ZYRqfOJz5vbbvYmfj65bp0EdsyqZuOxXzIcn2aUScQhskfvzyhV5XIUv81PQ==} + '@mantine/core@8.3.10': + resolution: {integrity: sha512-aKQFETN14v6GtM07b/G5yJneMM1yrgf9mNrTah6GVy5DvQM0AeutITT7toHqh5gxxwzdg/DoY+HQsv5zhqnc5g==} peerDependencies: - '@mantine/hooks': 8.3.9 + '@mantine/hooks': 8.3.10 react: ^18.x || ^19.x react-dom: ^18.x || ^19.x - '@mantine/dates@8.3.9': - resolution: {integrity: sha512-XOoGrY2hydqVrn/eBmhmJnZW6fAIOTOv0TLlN/a+C/sMxAgwNLqk+hqU6Rd3WE4Hm00J1BmbJJt/g/KrvnEpNg==} + '@mantine/dates@8.3.10': + resolution: {integrity: sha512-P1uZ+alYGp7fsmkfd+7Fur4AGrqT0X6BWLiVTomzrbyykA+m4TSwPyQjKfsDc7XRqaqx992br/U65T82zy+qGQ==} peerDependencies: - '@mantine/core': 8.3.9 - '@mantine/hooks': 8.3.9 + '@mantine/core': 8.3.10 + '@mantine/hooks': 8.3.10 dayjs: '>=1.0.0' react: ^18.x || ^19.x react-dom: ^18.x || ^19.x - '@mantine/dropzone@8.3.9': - resolution: {integrity: sha512-RXFpmHQnANMxa3qBssQyI/YS+Xzpr6N/vPWTXrAzjNrJApbwShlIKRDHNw4mmydYT3Wq9y86uzXFPsrPoaJWLA==} + '@mantine/dropzone@8.3.10': + resolution: {integrity: sha512-PqY9gZ7tpz34iKek+UImbK1LBmSmkMlspqkUecAsMwbBqD2WNLNINTl7pkOh20BdYOaX1GvfkXwTA4/6ya/1kg==} peerDependencies: - '@mantine/core': 8.3.9 - '@mantine/hooks': 8.3.9 + '@mantine/core': 8.3.10 + '@mantine/hooks': 8.3.10 react: ^18.x || ^19.x react-dom: ^18.x || ^19.x - '@mantine/form@8.3.9': - resolution: {integrity: sha512-G7eCo5TEWGcsWHrNGwp/o1yPcCYbaMdLq6SClN+wMhAA+qaEO1ZHf9mX8fSOX78nkaoyIrZHGOhbVXcZ7hmVrQ==} + '@mantine/form@8.3.10': + resolution: {integrity: sha512-TuBmCUIH0qHUig+y9My3bLL9CRoW4g9bijIF6743gqVh0o/daSwplc2TTVMj6sl+F1MR+SJiHtAC8FoR7fdhNw==} peerDependencies: react: ^18.x || ^19.x - '@mantine/hooks@8.3.9': - resolution: {integrity: sha512-Dfz7W0+K1cq4Gb1WFQCZn8tsMXkLH6MV409wZR/ToqsxdNDUMJ/xxbfnwEXWEZjXNJd1wDETHgc+cZG2lTe3Xw==} + '@mantine/hooks@8.3.10': + resolution: {integrity: sha512-bv+yYHl+keTIvakiDzVJMIjW+o8/Px0G3EdpCMFG+U2ux6SwQqluqoq+/kqrTtT6RaLvQ0fMxjpIULF2cu/xAg==} peerDependencies: react: ^18.x || ^19.x - '@mantine/modals@8.3.9': - resolution: {integrity: sha512-0WOikHgECJeWA/1TNf+sxOnpNwQjmpyph3XEhzFkgneimW6Ry7R6qd/i345CDLSu6kP6FGGRI73SUROiTcu2Ng==} + '@mantine/modals@8.3.10': + resolution: {integrity: sha512-XopCrP8dindhzSDazU47BgU8TVsiOyEG0u1UMJJ4u8TdvBctP7QVeJmGKj+B4MRHk2cHrjIF38dEGJhDgTITEg==} peerDependencies: - '@mantine/core': 8.3.9 - '@mantine/hooks': 8.3.9 + '@mantine/core': 8.3.10 + '@mantine/hooks': 8.3.10 react: ^18.x || ^19.x react-dom: ^18.x || ^19.x - '@mantine/notifications@8.3.9': - resolution: {integrity: sha512-emUdoCyaccf/NuNmJ4fQgloJ7hEod0Pde7XIoD9xUUztVchL143oWRU2gYm6cwqzSyjpjTaqPXfz5UvEBRYjZw==} + '@mantine/notifications@8.3.10': + resolution: {integrity: sha512-0aVpRCyn9u0wuryBnFu1jOwBYw6xGeaNNtTcTUnSvkL6NAypfPon6JG7Wsekf3IuWSTLBjhYaFEIEd4nh7VDpg==} peerDependencies: - '@mantine/core': 8.3.9 - '@mantine/hooks': 8.3.9 + '@mantine/core': 8.3.10 + '@mantine/hooks': 8.3.10 react: ^18.x || ^19.x react-dom: ^18.x || ^19.x - '@mantine/spotlight@8.3.9': - resolution: {integrity: sha512-leU94fz2u0BGHnoC1BBFI69lhgZ5VRhzzsdfIaN3A4WZ6zHT5BodY8BupnQqpmRHvqHnW/sagNiltB80cUGy2Q==} + '@mantine/spotlight@8.3.10': + resolution: {integrity: sha512-0GfQd/smRcd5u0o6Ad7J9ZEWLcZZ81h9/Z9qUnzIlJeYjXqJdr40MMqDxNsXgZEDKscPJkggZMqMiRZXhFbdNQ==} peerDependencies: - '@mantine/core': 8.3.9 - '@mantine/hooks': 8.3.9 + '@mantine/core': 8.3.10 + '@mantine/hooks': 8.3.10 react: ^18.x || ^19.x react-dom: ^18.x || ^19.x - '@mantine/store@8.3.9': - resolution: {integrity: sha512-Z4tYW597mD3NxHLlJ3OJ1aKucmwrD9nhqobz+142JNw01aHqzKjxVXlu3L5GGa7F3u3OjXJk/qb1QmUs4sU+Jw==} + '@mantine/store@8.3.10': + resolution: {integrity: sha512-38t1UivcucZo9hQq27F/eqR5GvovNs4NHEz6DchOuZzV5IJWqO8+T07ivb8wct47ovYe42rPfLcaOdnIEvMsJA==} peerDependencies: react: ^18.x || ^19.x - '@mantine/tiptap@8.3.9': - resolution: {integrity: sha512-DNWkX7+HDka2SwrgZA2VqLD27wyn/Xqs7mAAwgdML6qJfAKpHeOdwPZmwA6LBThEwNtAaUK+vmPObv5ULF4AOg==} + '@mantine/tiptap@8.3.10': + resolution: {integrity: sha512-gvHzn9ZPcQJyfHKfZxk9awwM8y0jqg3aWGEsOq0Ii47x9s5OD6PRo5NQeYUtTlh1md4toqIjVvolb9xWZGJpYg==} peerDependencies: - '@mantine/core': 8.3.9 - '@mantine/hooks': 8.3.9 + '@mantine/core': 8.3.10 + '@mantine/hooks': 8.3.10 '@tiptap/extension-link': '>=2.1.12' '@tiptap/react': '>=2.1.12' react: ^18.x || ^19.x @@ -3786,60 +3712,60 @@ packages: '@ndaidong/bellajs@12.0.1': resolution: {integrity: sha512-1iY42uiHz0cxNMbde7O3zVN+ZX1viOOUOBRt6ht6lkRZbSjwOnFV34Zv4URp3hGzEe6L9Byk7BOq/41H0PzAOQ==} - '@next/env@16.0.7': - resolution: {integrity: sha512-gpaNgUh5nftFKRkRQGnVi5dpcYSKGcZZkQffZ172OrG/XkrnS7UBTQ648YY+8ME92cC4IojpI2LqTC8sTDhAaw==} + '@next/env@16.0.10': + resolution: {integrity: sha512-8tuaQkyDVgeONQ1MeT9Mkk8pQmZapMKFh5B+OrFUlG3rVmYTXcXlBetBgTurKXGaIZvkoqRT9JL5K3phXcgang==} - '@next/eslint-plugin-next@16.0.7': - resolution: {integrity: sha512-hFrTNZcMEG+k7qxVxZJq3F32Kms130FAhG8lvw2zkKBgAcNOJIxlljNiCjGygvBshvaGBdf88q2CqWtnqezDHA==} + '@next/eslint-plugin-next@16.0.10': + resolution: {integrity: sha512-b2NlWN70bbPLmfyoLvvidPKWENBYYIe017ZGUpElvQjDytCWgxPJx7L9juxHt0xHvNVA08ZHJdOyhGzon/KJuw==} - '@next/swc-darwin-arm64@16.0.7': - resolution: {integrity: sha512-LlDtCYOEj/rfSnEn/Idi+j1QKHxY9BJFmxx7108A6D8K0SB+bNgfYQATPk/4LqOl4C0Wo3LACg2ie6s7xqMpJg==} + '@next/swc-darwin-arm64@16.0.10': + resolution: {integrity: sha512-4XgdKtdVsaflErz+B5XeG0T5PeXKDdruDf3CRpnhN+8UebNa5N2H58+3GDgpn/9GBurrQ1uWW768FfscwYkJRg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@16.0.7': - resolution: {integrity: sha512-rtZ7BhnVvO1ICf3QzfW9H3aPz7GhBrnSIMZyr4Qy6boXF0b5E3QLs+cvJmg3PsTCG2M1PBoC+DANUi4wCOKXpA==} + '@next/swc-darwin-x64@16.0.10': + resolution: {integrity: sha512-spbEObMvRKkQ3CkYVOME+ocPDFo5UqHb8EMTS78/0mQ+O1nqE8toHJVioZo4TvebATxgA8XMTHHrScPrn68OGw==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@16.0.7': - resolution: {integrity: sha512-mloD5WcPIeIeeZqAIP5c2kdaTa6StwP4/2EGy1mUw8HiexSHGK/jcM7lFuS3u3i2zn+xH9+wXJs6njO7VrAqww==} + '@next/swc-linux-arm64-gnu@16.0.10': + resolution: {integrity: sha512-uQtWE3X0iGB8apTIskOMi2w/MKONrPOUCi5yLO+v3O8Mb5c7K4Q5KD1jvTpTF5gJKa3VH/ijKjKUq9O9UhwOYw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [glibc] - '@next/swc-linux-arm64-musl@16.0.7': - resolution: {integrity: sha512-+ksWNrZrthisXuo9gd1XnjHRowCbMtl/YgMpbRvFeDEqEBd523YHPWpBuDjomod88U8Xliw5DHhekBC3EOOd9g==} + '@next/swc-linux-arm64-musl@16.0.10': + resolution: {integrity: sha512-llA+hiDTrYvyWI21Z0L1GiXwjQaanPVQQwru5peOgtooeJ8qx3tlqRV2P7uH2pKQaUfHxI/WVarvI5oYgGxaTw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [musl] - '@next/swc-linux-x64-gnu@16.0.7': - resolution: {integrity: sha512-4WtJU5cRDxpEE44Ana2Xro1284hnyVpBb62lIpU5k85D8xXxatT+rXxBgPkc7C1XwkZMWpK5rXLXTh9PFipWsA==} + '@next/swc-linux-x64-gnu@16.0.10': + resolution: {integrity: sha512-AK2q5H0+a9nsXbeZ3FZdMtbtu9jxW4R/NgzZ6+lrTm3d6Zb7jYrWcgjcpM1k8uuqlSy4xIyPR2YiuUr+wXsavA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [glibc] - '@next/swc-linux-x64-musl@16.0.7': - resolution: {integrity: sha512-HYlhqIP6kBPXalW2dbMTSuB4+8fe+j9juyxwfMwCe9kQPPeiyFn7NMjNfoFOfJ2eXkeQsoUGXg+O2SE3m4Qg2w==} + '@next/swc-linux-x64-musl@16.0.10': + resolution: {integrity: sha512-1TDG9PDKivNw5550S111gsO4RGennLVl9cipPhtkXIFVwo31YZ73nEbLjNC8qG3SgTz/QZyYyaFYMeY4BKZR/g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [musl] - '@next/swc-win32-arm64-msvc@16.0.7': - resolution: {integrity: sha512-EviG+43iOoBRZg9deGauXExjRphhuYmIOJ12b9sAPy0eQ6iwcPxfED2asb/s2/yiLYOdm37kPaiZu8uXSYPs0Q==} + '@next/swc-win32-arm64-msvc@16.0.10': + resolution: {integrity: sha512-aEZIS4Hh32xdJQbHz121pyuVZniSNoqDVx1yIr2hy+ZwJGipeqnMZBJHyMxv2tiuAXGx6/xpTcQJ6btIiBjgmg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@16.0.7': - resolution: {integrity: sha512-gniPjy55zp5Eg0896qSrf3yB1dw4F/3s8VK1ephdsZZ129j2n6e1WqCbE2YgcKhW9hPB9TVZENugquWJD5x0ug==} + '@next/swc-win32-x64-msvc@16.0.10': + resolution: {integrity: sha512-E+njfCoFLb01RAFEnGZn6ERoOqhK1Gl3Lfz1Kjnj0Ulfu7oJbuMyvBKNj/bw8XZnenHDASlygTjZICQW+rYW1Q==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -4097,9 +4023,6 @@ packages: '@polka/url@1.0.0-next.25': resolution: {integrity: sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==} - '@popperjs/core@2.11.8': - resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} - '@protobufjs/aspromise@1.1.2': resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} @@ -4140,8 +4063,8 @@ packages: '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 - '@rolldown/pluginutils@1.0.0-beta.47': - resolution: {integrity: sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==} + '@rolldown/pluginutils@1.0.0-beta.53': + resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==} '@rollup/pluginutils@5.1.0': resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} @@ -4421,8 +4344,8 @@ packages: peerDependencies: semantic-release: '>=24.1.0' - '@semantic-release/npm@13.1.2': - resolution: {integrity: sha512-9rtshDTNlzYrC7uSBtB1vHqFzFZaNHigqkkCH5Ls4N/BSlVOenN5vtwHYxjAR4jf1hNvWSVwL4eIFTHONYckkw==} + '@semantic-release/npm@13.1.3': + resolution: {integrity: sha512-q7zreY8n9V0FIP1Cbu63D+lXtRAVAIWb30MH5U3TdrfXt6r2MIrWCY0whAImN53qNvSGp0Zt07U95K+Qp9GpEg==} engines: {node: ^22.14.0 || >= 24.10.0} peerDependencies: semantic-release: '>=20.1.0' @@ -4630,232 +4553,241 @@ packages: peerDependencies: vue: ^2.7.0 || ^3.0.0 - '@testcontainers/mysql@11.9.0': - resolution: {integrity: sha512-XayyQs0QAKIlYVngUOyaqwjGobERXOsvjgw6vyvdYavUDx8AXpNEe2dc4sCcXSH0CgiTTcF3SzFuodjXvDecww==} + '@testcontainers/mysql@11.10.0': + resolution: {integrity: sha512-0DLD0ClFFLewUzD+yHm9lBHPn4OTXEGdgf1Jn0Xnw+noPVol5DeP+GqSUXZ3nfde36l8g5uRPNEgHFBOolVdPA==} - '@testcontainers/postgresql@11.9.0': - resolution: {integrity: sha512-beLyLdLygFllktviM132Xd6tQ4i5FnuyZP+4BQEjUb5sJYHYnIrV/ZBzRRflIlF8gugt1GXgudkmr/HxM9vtKw==} + '@testcontainers/postgresql@11.10.0': + resolution: {integrity: sha512-d6QeN3KkXLJBdt0T6X3KKtdkHbaZdzCRPo133FSG8yOoGofQAYghtau39iUdeF9GAN8UTWZAxio40uYKBSV7xw==} - '@testcontainers/redis@11.9.0': - resolution: {integrity: sha512-nDDsi8d17mYK44JAkX4tI91kgoQAT4E02UJK+d2BbcYYLZJtS3tP+w+MUkDuHWrUMlqb3HiyAdhcPz2ezxn2TA==} + '@testcontainers/redis@11.10.0': + resolution: {integrity: sha512-w/Hnv1IH8jJ4wjIgpSzoll1KABz2L28+i6JAZVSZuSzQPqeTeFa3mZHnRcdKJggjEIMDwpFlqjGXYRYKNAk0Fw==} - '@tiptap/core@2.27.1': - resolution: {integrity: sha512-nkerkl8syHj44ZzAB7oA2GPmmZINKBKCa79FuNvmGJrJ4qyZwlkDzszud23YteFZEytbc87kVd/fP76ROS6sLg==} + '@tiptap/core@3.13.0': + resolution: {integrity: sha512-iUelgiTMgPVMpY5ZqASUpk8mC8HuR9FWKaDzK27w9oWip9tuB54Z8mePTxNcQaSPb6ErzEaC8x8egrRt7OsdGQ==} peerDependencies: - '@tiptap/pm': ^2.7.0 + '@tiptap/pm': ^3.13.0 - '@tiptap/extension-blockquote@2.27.1': - resolution: {integrity: sha512-QrUX3muElDrNjKM3nqCSAtm3H3pT33c6ON8kwRiQboOAjT/9D57Cs7XEVY7r6rMaJPeKztrRUrNVF9w/w/6B0A==} + '@tiptap/extension-blockquote@3.13.0': + resolution: {integrity: sha512-K1z/PAIIwEmiWbzrP//4cC7iG1TZknDlF1yb42G7qkx2S2X4P0NiqX7sKOej3yqrPjKjGwPujLMSuDnCF87QkQ==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.13.0 - '@tiptap/extension-bold@2.27.1': - resolution: {integrity: sha512-g4l4p892x/r7mhea8syp3fNYODxsDrimgouQ+q4DKXIgQmm5+uNhyuEPexP3I8TFNXqQ4DlMNFoM9yCqk97etQ==} + '@tiptap/extension-bold@3.13.0': + resolution: {integrity: sha512-VYiDN9EEwR6ShaDLclG8mphkb/wlIzqfk7hxaKboq1G+NSDj8PcaSI9hldKKtTCLeaSNu6UR5nkdu/YHdzYWTw==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.13.0 - '@tiptap/extension-bubble-menu@2.27.1': - resolution: {integrity: sha512-ki1R27VsSvY2tT9Q2DIlcATwLOoEjf5DsN+5sExarQ8S/ZxT/tvIjRxB8Dx7lb2a818W5f/NER26YchGtmHfpg==} + '@tiptap/extension-bubble-menu@3.13.0': + resolution: {integrity: sha512-qZ3j2DBsqP9DjG2UlExQ+tHMRhAnWlCKNreKddKocb/nAFrPdBCtvkqIEu+68zPlbLD4ukpoyjUklRJg+NipFg==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/core': ^3.13.0 + '@tiptap/pm': ^3.13.0 - '@tiptap/extension-bullet-list@2.27.1': - resolution: {integrity: sha512-5FmnfXkJ76wN4EbJNzBhAlmQxho8yEMIJLchTGmXdsD/n/tsyVVtewnQYaIOj/Z7naaGySTGDmjVtLgTuQ+Sxw==} + '@tiptap/extension-bullet-list@3.13.0': + resolution: {integrity: sha512-fFQmmEUoPzRGiQJ/KKutG35ZX21GE+1UCDo8Q6PoWH7Al9lex47nvyeU1BiDYOhcTKgIaJRtEH5lInsOsRJcSA==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/extension-list': ^3.13.0 - '@tiptap/extension-code-block@2.27.1': - resolution: {integrity: sha512-wCI5VIOfSAdkenCWFvh4m8FFCJ51EOK+CUmOC/PWUjyo2Dgn8QC8HMi015q8XF7886T0KvYVVoqxmxJSUDAYNg==} + '@tiptap/extension-code-block@3.13.0': + resolution: {integrity: sha512-kIwfQ4iqootsWg9e74iYJK54/YMIj6ahUxEltjZRML5z/h4gTDcQt2eTpnEC8yjDjHeUVOR94zH9auCySyk9CQ==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/core': ^3.13.0 + '@tiptap/pm': ^3.13.0 - '@tiptap/extension-code@2.27.1': - resolution: {integrity: sha512-i65wUGJevzBTIIUBHBc1ggVa27bgemvGl/tY1/89fEuS/0Xmre+OQjw8rCtSLevoHSiYYLgLRlvjtUSUhE4kgg==} + '@tiptap/extension-code@3.13.0': + resolution: {integrity: sha512-sF5raBni6iSVpXWvwJCAcOXw5/kZ+djDHx1YSGWhopm4+fsj0xW7GvVO+VTwiFjZGKSw+K5NeAxzcQTJZd3Vhw==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.13.0 - '@tiptap/extension-color@2.27.1': - resolution: {integrity: sha512-raYRsdG2tZvVvY1LV/VTZnDG44Y0xRBwo5CZEat0OUqdx34dfvCtYm8HIOTyWBwr7OOW+yR4O1Vc2zFkmfthZw==} + '@tiptap/extension-color@3.13.0': + resolution: {integrity: sha512-I+PTZ31p6GhCjUVpCh1qertTK6T07MVGuLm/LP+c14ByMoZ0L0nVgw0YbWhDqLYWbkI9davv0fuI3dQ0gKSPlA==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/extension-text-style': ^2.7.0 + '@tiptap/extension-text-style': ^3.13.0 - '@tiptap/extension-document@2.27.1': - resolution: {integrity: sha512-NtJzJY7Q/6XWjpOm5OXKrnEaofrcc1XOTYlo/SaTwl8k2bZo918Vl0IDBWhPVDsUN7kx767uHwbtuQZ+9I82hA==} + '@tiptap/extension-document@3.13.0': + resolution: {integrity: sha512-RjU7hTJwjKXIdY57o/Pc+Yr8swLkrwT7PBQ/m+LCX5oO/V2wYoWCjoBYnK5KSHrWlNy/aLzC33BvLeqZZ9nzlQ==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.13.0 - '@tiptap/extension-dropcursor@2.27.1': - resolution: {integrity: sha512-3MBQRGHHZ0by3OT0CWbLKS7J3PH9PpobrXjmIR7kr0nde7+bHqxXiVNuuIf501oKU9rnEUSedipSHkLYGkmfsA==} + '@tiptap/extension-dropcursor@3.13.0': + resolution: {integrity: sha512-m7GPT3c/83ni+bbU8c+3dpNa8ug+aQ4phNB1Q52VQG3oTonDJnZS7WCtn3lB/Hi1LqoqMtEHwhepU2eD+JeXqQ==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/extensions': ^3.13.0 - '@tiptap/extension-floating-menu@2.27.1': - resolution: {integrity: sha512-nUk/8DbiXO69l6FDwkWso94BTf52IBoWALo+YGWT6o+FO6cI9LbUGghEX2CdmQYXCvSvwvISF2jXeLQWNZvPZQ==} + '@tiptap/extension-floating-menu@3.13.0': + resolution: {integrity: sha512-OsezV2cMofZM4c13gvgi93IEYBUzZgnu8BXTYZQiQYekz4bX4uulBmLa1KOA9EN71FzS+SoLkXHU0YzlbLjlxA==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@floating-ui/dom': ^1.0.0 + '@tiptap/core': ^3.13.0 + '@tiptap/pm': ^3.13.0 - '@tiptap/extension-gapcursor@2.27.1': - resolution: {integrity: sha512-A9e1jr+jGhDWzNSXtIO6PYVYhf5j/udjbZwMja+wCE/3KvZU9V3IrnGKz1xNW+2Q2BDOe1QO7j5uVL9ElR6nTA==} + '@tiptap/extension-gapcursor@3.13.0': + resolution: {integrity: sha512-KVxjQKkd964nin+1IdM2Dvej/Jy4JTMcMgq5seusUhJ9T9P8F9s2D5Iefwgkps3OCzub/aF+eAsZe+1P5KSIgA==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/extensions': ^3.13.0 - '@tiptap/extension-hard-break@2.27.1': - resolution: {integrity: sha512-W4hHa4Io6QCTwpyTlN6UAvqMIQ7t56kIUByZhyY9EWrg/+JpbfpxE1kXFLPB4ZGgwBknFOw+e4bJ1j3oAbTJFw==} + '@tiptap/extension-hard-break@3.13.0': + resolution: {integrity: sha512-nH1OBaO+/pakhu+P1jF208mPgB70IKlrR/9d46RMYoYbqJTNf4KVLx5lHAOHytIhjcNg+MjyTfJWfkK+dyCCyg==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.13.0 - '@tiptap/extension-heading@2.27.1': - resolution: {integrity: sha512-6xoC7igZlW1EmnQ5WVH9IL7P1nCQb3bBUaIDLvk7LbweEogcTUECI4Xg1vxMOVmj9tlDe1I4BsgfcKpB5KEsZw==} + '@tiptap/extension-heading@3.13.0': + resolution: {integrity: sha512-8VKWX8waYPtUWN97J89em9fOtxNteh6pvUEd0htcOAtoxjt2uZjbW5N4lKyWhNKifZBrVhH2Cc2NUPuftCVgxw==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.13.0 - '@tiptap/extension-highlight@2.27.1': - resolution: {integrity: sha512-ntuYX09tvHQE/R/8WbTOxbFuQhRr2jhTkKz/gLwDD2o8IhccSy3f0nm+mVmVamKQnbsBBbLohojd5IGOnX9f1A==} + '@tiptap/extension-highlight@3.13.0': + resolution: {integrity: sha512-VlXyO8gWhWd0W5ln4Qyr/P1RoAVZkk9ge0jjNsb1bOc078wuvufQ9f6DquMxx0WpcaCXy+DBQLE0GUUqoLvjsQ==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.13.0 - '@tiptap/extension-history@2.27.1': - resolution: {integrity: sha512-K8PHC9gegSAt0wzSlsd4aUpoEyIJYOmVVeyniHr1P1mIblW1KYEDbRGbDlrLALTyUEfMcBhdIm8zrB9X2Nihvg==} + '@tiptap/extension-horizontal-rule@3.13.0': + resolution: {integrity: sha512-ZUFyORtjj22ib8ykbxRhWFQOTZjNKqOsMQjaAGof30cuD2DN5J5pMz7Haj2fFRtLpugWYH+f0Mi+WumQXC3hCw==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/core': ^3.13.0 + '@tiptap/pm': ^3.13.0 - '@tiptap/extension-horizontal-rule@2.27.1': - resolution: {integrity: sha512-WxXWGEEsqDmGIF2o9av+3r9Qje4CKrqrpeQY6aRO5bxvWX9AabQCfasepayBok6uwtvNzh3Xpsn9zbbSk09dNA==} + '@tiptap/extension-image@3.13.0': + resolution: {integrity: sha512-223uzLUkIa1rkK7aQK3AcIXe6LbCtmnpVb7sY5OEp+LpSaSPyXwyrZ4A0EO1o98qXG68/0B2OqMntFtA9c5Fbw==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/core': ^3.13.0 - '@tiptap/extension-image@2.27.1': - resolution: {integrity: sha512-wu3vMKDYWJwKS6Hrw5PPCKBO2RxyHNeFLiA/uDErEV7axzNpievK/U9DyaDXmtK3K/h1XzJAJz19X+2d/pY68w==} + '@tiptap/extension-italic@3.13.0': + resolution: {integrity: sha512-XbVTgmzk1kgUMTirA6AGdLTcKHUvEJoh3R4qMdPtwwygEOe7sBuvKuLtF6AwUtpnOM+Y3tfWUTNEDWv9AcEdww==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.13.0 - '@tiptap/extension-italic@2.27.1': - resolution: {integrity: sha512-rcm0GyniWW0UhcNI9+1eIK64GqWQLyIIrWGINslvqSUoBc+WkfocLvv4CMpRkzKlfsAxwVIBuH2eLxHKDtAREA==} + '@tiptap/extension-link@3.13.0': + resolution: {integrity: sha512-LuFPJ5GoL12GHW4A+USsj60O90pLcwUPdvEUSWewl9USyG6gnLnY/j5ZOXPYH7LiwYW8+lhq7ABwrDF2PKyBbA==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.13.0 + '@tiptap/pm': ^3.13.0 - '@tiptap/extension-link@2.27.1': - resolution: {integrity: sha512-cCwWPZsnVh9MXnGOqSIRXPPuUixRDK8eMN2TvqwbxUBb1TU7b/HtNvfMU4tAOqAuMRJ0aJkFuf3eB0Gi8LVb1g==} + '@tiptap/extension-list-item@3.13.0': + resolution: {integrity: sha512-63NbcS/XeQP2jcdDEnEAE3rjJICDj8y1SN1h/MsJmSt1LusnEo8WQ2ub86QELO6XnD3M04V03cY6Knf6I5mTkw==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/extension-list': ^3.13.0 - '@tiptap/extension-list-item@2.27.1': - resolution: {integrity: sha512-dtsxvtzxfwOJP6dKGf0vb2MJAoDF2NxoiWzpq0XTvo7NGGYUHfuHjX07Zp0dYqb4seaDXjwsi5BIQUOp3+WMFQ==} + '@tiptap/extension-list-keymap@3.13.0': + resolution: {integrity: sha512-P+HtIa1iwosb1feFc8B/9MN5EAwzS+/dZ0UH0CTF2E4wnp5Z9OMxKl1IYjfiCwHzZrU5Let+S/maOvJR/EmV0g==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/extension-list': ^3.13.0 - '@tiptap/extension-ordered-list@2.27.1': - resolution: {integrity: sha512-U1/sWxc2TciozQsZjH35temyidYUjvroHj3PUPzPyh19w2fwKh1NSbFybWuoYs6jS3XnMSwnM2vF52tOwvfEmA==} + '@tiptap/extension-list@3.13.0': + resolution: {integrity: sha512-MMFH0jQ4LeCPkJJFyZ77kt6eM/vcKujvTbMzW1xSHCIEA6s4lEcx9QdZMPpfmnOvTzeoVKR4nsu2t2qT9ZXzAw==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.13.0 + '@tiptap/pm': ^3.13.0 - '@tiptap/extension-paragraph@2.27.1': - resolution: {integrity: sha512-R3QdrHcUdFAsdsn2UAIvhY0yWyHjqGyP/Rv8RRdN0OyFiTKtwTPqreKMHKJOflgX4sMJl/OpHTpNG1Kaf7Lo2A==} + '@tiptap/extension-ordered-list@3.13.0': + resolution: {integrity: sha512-QuDyLzuK/3vCvx9GeKhgvHWrGECBzmJyAx6gli2HY+Iil7XicbfltV4nvhIxgxzpx3LDHLKzJN9pBi+2MzX60g==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/extension-list': ^3.13.0 - '@tiptap/extension-placeholder@2.27.1': - resolution: {integrity: sha512-UbXaibHHFE+lOTlw/vs3jPzBoj1sAfbXuTAhXChjgYIcTTY5Cr6yxwcymLcimbQ79gf04Xkua2FCN3YsJxIFmw==} + '@tiptap/extension-paragraph@3.13.0': + resolution: {integrity: sha512-9csQde1i0yeZI5oQQ9e1GYNtGL2JcC2d8Fwtw9FsGC8yz2W0h+Fmk+3bc2kobbtO5LGqupSc1fKM8fAg5rSRDg==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/core': ^3.13.0 - '@tiptap/extension-strike@2.27.1': - resolution: {integrity: sha512-S9I//K8KPgfFTC5I5lorClzXk0g4lrAv9y5qHzHO5EOWt7AFl0YTg2oN8NKSIBK4bHRnPIrjJJKv+dDFnUp5jQ==} + '@tiptap/extension-placeholder@3.13.0': + resolution: {integrity: sha512-Au4ktRBraQktX9gjSzGWyJV6kPof7+kOhzE8ej+rOMjIrHbx3DCHy1CJWftSO9BbqIyonjsFmm4nE+vjzZ3Z5Q==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/extensions': ^3.13.0 - '@tiptap/extension-table-cell@2.27.1': - resolution: {integrity: sha512-VowNmz1kub2qfntWkU8jGA6DoCl9xjJBWSypuQIeiN/IRId3BMrJodT26pTNJ3ChDMtYaanWaUvYqckRxgTC2A==} + '@tiptap/extension-strike@3.13.0': + resolution: {integrity: sha512-VHhWNqTAMOfrC48m2FcPIZB0nhl6XHQviAV16SBc+EFznKNv9tQUsqQrnuQ2y6ZVfqq5UxvZ3hKF/JlN/Ff7xw==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.13.0 - '@tiptap/extension-table-header@2.27.1': - resolution: {integrity: sha512-lSbGB6kBp/sTVzAWl4v7v7ztL5XU3aTdlS7FhfGjpdsxd4zPKYG8kx+Uxgq25W9/BlCbnqHnO0poAMfOlspDQw==} + '@tiptap/extension-table-cell@3.13.0': + resolution: {integrity: sha512-dBOPeLe2NqymZI8reS2yZRIcgfIYHudCq4lDWZBJ5NiMvoy0b/fVe1qYSP4EcfBQMpfgAP0E05bMB3C7p1kmkw==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/extension-table': ^3.13.0 - '@tiptap/extension-table-row@2.27.1': - resolution: {integrity: sha512-3xtlmZ6NWDi5a42gK0qQQTeBUpJ2j1o7qyXTFkhQaJAeIFEqsemgSRhgXZxbwSmQQZsPJ/86KWBNVkT0FaRFDw==} + '@tiptap/extension-table-header@3.13.0': + resolution: {integrity: sha512-ZA5lhcek0VlKupqj3WFgE6bj5vNRmxcpDqjYc6RWaxuva4dYfEQSqdlG+3a53Sr53le0grfBpolM8zJFCVYrcw==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/extension-table': ^3.13.0 - '@tiptap/extension-table@2.27.1': - resolution: {integrity: sha512-iOoOo0vYFzAogAZlw36DgmFfNM5vOkLqnApm81soO/YWpqtKAvBn+TMY4ss4OMDsOefUzBa6xqOJ0gJR5ZygjA==} + '@tiptap/extension-table-row@3.13.0': + resolution: {integrity: sha512-FdsMTIBNleZtWJEhXE/PBxkB8L9YJtnKs7YZNesLKqLn6/sWZl+X5e5SAAN/N3DLOEAwNCa/m0C3A1DWGNz/tw==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/extension-table': ^3.13.0 - '@tiptap/extension-task-item@2.27.1': - resolution: {integrity: sha512-vaEtdos+9jApD6yRfD6F/xShikiZFHi7I0nswAmGKT/kE1wmHCUxme8OFMe7642e2OK0lqgHsUaOLxP/0nZJ5A==} + '@tiptap/extension-table@3.13.0': + resolution: {integrity: sha512-LcH9KE4QBUJ6IPwt1Uo5iU7zatFjUUvXbctIu2fKQ9nqJ7nNSFxRhkNyporVFkTWYH7/rb0qMoF1VxSUGefG5w==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/core': ^3.13.0 + '@tiptap/pm': ^3.13.0 - '@tiptap/extension-task-list@2.27.1': - resolution: {integrity: sha512-KRlYOZ6kdURvAspUrLVsC7mLkVW2DYhpj+7QxH7gVDZuAuoPUEmpJVcBVPq7GhPF9PccaRLru+n1Ege5VqvZ+Q==} + '@tiptap/extension-task-item@3.13.0': + resolution: {integrity: sha512-4+IkwoKchHQSxzEtVgpjSTJ/ey++Vf9WFiaY0o1ijmvQ7XvvXv4ZvO7uJrawShCIf0tmA56fjuMcZc5qJITQKQ==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/extension-list': ^3.13.0 - '@tiptap/extension-text-align@2.27.1': - resolution: {integrity: sha512-D7dLPk7y5mDn9ZNANQ4K2gCq4vy+Emm5AdeWOGzNeqJsYrBotiQYXd9rb1QYjdup2kzAoKduMTUXV92ujo5cEg==} + '@tiptap/extension-task-list@3.13.0': + resolution: {integrity: sha512-oqJrLR3yKlSwDDr6CCF27g2bDybFcvkTyn/HReFpteziGeQs2DWJ5sY+tSWX1eKhnTnZbEjG5G/oie/vJqIIow==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/extension-list': ^3.13.0 - '@tiptap/extension-text-style@2.27.1': - resolution: {integrity: sha512-NagQ9qLk0Ril83gfrk+C65SvTqPjL3WVnLF2arsEVnCrxcx3uDOvdJW67f/K5HEwEHsoqJ4Zq9Irco/koXrOXA==} + '@tiptap/extension-text-align@3.13.0': + resolution: {integrity: sha512-hebIus9tdXWb+AmhO+LTeUxZLdb0tqwdeaL/0wYxJQR5DeCTlJe6huXacMD/BkmnlEpRhxzQH0FrmXAd0d4Wgg==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.13.0 - '@tiptap/extension-text@2.27.1': - resolution: {integrity: sha512-a4GCT+GZ9tUwl82F4CEum9/+WsuW0/De9Be/NqrMmi7eNfAwbUTbLCTFU0gEvv25WMHCoUzaeNk/qGmzeVPJ1Q==} + '@tiptap/extension-text-style@3.13.0': + resolution: {integrity: sha512-M7ob3pfYNYgFPihncEp33r9477hXQgC8j3iU8BsewvPlSx2bMSy5jp2XHDXyEX8dV6flr7acH4GkXXw+DHpaPA==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.13.0 - '@tiptap/extension-underline@2.27.1': - resolution: {integrity: sha512-fPTmfJFAQWg1O/os1pYSPVdtvly6eW/w5sDofG7pre+bdQUN+8s1cZYelSuj/ltNVioRaB2Ws7tvNgnHL0aAJQ==} + '@tiptap/extension-text@3.13.0': + resolution: {integrity: sha512-VcZIna93rixw7hRkHGCxDbL3kvJWi80vIT25a2pXg0WP1e7Pi3nBYvZIL4SQtkbBCji9EHrbZx3p8nNPzfazYw==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.13.0 - '@tiptap/pm@2.27.1': - resolution: {integrity: sha512-ijKo3+kIjALthYsnBmkRXAuw2Tswd9gd7BUR5OMfIcjGp8v576vKxOxrRfuYiUM78GPt//P0sVc1WV82H5N0PQ==} - - '@tiptap/react@2.27.1': - resolution: {integrity: sha512-leJximSjYJuhLJQv9azOP9R7w6zuxVgKOHYT4w83Gte7GhWMpNL6xRWzld280vyq/YW/cSYjPb/8ESEOgKNBdQ==} + '@tiptap/extension-underline@3.13.0': + resolution: {integrity: sha512-VDQi+UYw0tFnfghpthJTFmtJ3yx90kXeDwFvhmT8G+O+si5VmP05xYDBYBmYCix5jqKigJxEASiBL0gYOgMDEg==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/core': ^3.13.0 + + '@tiptap/extensions@3.13.0': + resolution: {integrity: sha512-i7O0ptSibEtTy+2PIPsNKEvhTvMaFJg1W4Oxfnbuxvaigs7cJV9Q0lwDUcc7CPsNw2T1+44wcxg431CzTvdYoA==} + peerDependencies: + '@tiptap/core': ^3.13.0 + '@tiptap/pm': ^3.13.0 + + '@tiptap/pm@3.13.0': + resolution: {integrity: sha512-WKR4ucALq+lwx0WJZW17CspeTpXorbIOpvKv5mulZica6QxqfMhn8n1IXCkDws/mCoLRx4Drk5d377tIjFNsvQ==} + + '@tiptap/react@3.13.0': + resolution: {integrity: sha512-VqpqNZ9qtPr3pWK4NsZYxXgLSEiAnzl6oS7tEGmkkvJbcGSC+F7R13Xc9twv/zT5QCLxaHdEbmxHbuAIkrMgJQ==} + peerDependencies: + '@tiptap/core': ^3.13.0 + '@tiptap/pm': ^3.13.0 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react-dom': ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tiptap/starter-kit@2.27.1': - resolution: {integrity: sha512-uQQlP0Nmn9eq19qm8YoOeloEfmcGbPpB1cujq54Q6nPgxaBozR7rE7tXbFTinxRW2+Hr7XyNWhpjB7DMNkdU2Q==} + '@tiptap/starter-kit@3.13.0': + resolution: {integrity: sha512-Ojn6sRub04CRuyQ+9wqN62JUOMv+rG1vXhc2s6DCBCpu28lkCMMW+vTe7kXJcEdbot82+5swPbERw9vohswFzg==} '@tootallnate/quickjs-emscripten@0.23.0': resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} - '@trpc/client@11.7.2': - resolution: {integrity: sha512-OQxqUMfpDvjcszo9dbnqWQXnW2L5IbrKSz2H7l8s+mVM3EvYw7ztQ/gjFIN3iy0NcamiQfd4eE6qjcb9Lm+63A==} + '@trpc/client@11.8.0': + resolution: {integrity: sha512-imJQeESX1hAapDaC4JB91yvXg41AZfBuTh/scnEiN/hAubZa5s/ikp0n+w29q2GCf+hREkr3WptUFKFJoDAIug==} peerDependencies: - '@trpc/server': 11.7.2 + '@trpc/server': 11.8.0 typescript: '>=5.7.2' - '@trpc/next@11.7.2': - resolution: {integrity: sha512-eZQeZag+/aJMxV6ucfyVdcgE7I6o88tldyBi+AFVCD8fPUjssRT7qOqR/THSaTLHjDl20Ok9gN0Sn1bmMEa+1w==} + '@trpc/next@11.8.0': + resolution: {integrity: sha512-aMS1itkddKCeZEmd4fV9jm9+HMob4R684FfNqXdnGkw0acfdeUpk4hihs8gml+fMqgPRv6LcgsZlOb4YpOOjbg==} peerDependencies: '@tanstack/react-query': ^5.59.15 - '@trpc/client': 11.7.2 - '@trpc/react-query': 11.7.2 - '@trpc/server': 11.7.2 + '@trpc/client': 11.8.0 + '@trpc/react-query': 11.8.0 + '@trpc/server': 11.8.0 next: '*' react: '>=16.8.0' react-dom: '>=16.8.0' @@ -4866,27 +4798,27 @@ packages: '@trpc/react-query': optional: true - '@trpc/react-query@11.7.2': - resolution: {integrity: sha512-IcLDMqx2mvlGRxkr0/m37TtPvRQ8nonITH3EwYv436x0Igx8eduR9z4tdgGBsjJY9e5W1G7cZ4zKCwrizSimFQ==} + '@trpc/react-query@11.8.0': + resolution: {integrity: sha512-zJG22PqhGUBq6ke58McGxRBGWGDTHlmsgpxp/rQx8iT2yi0Ja1H2/UcSsjZ/MG65DhCfpKvMVH0OVivTn9FWwA==} peerDependencies: '@tanstack/react-query': ^5.80.3 - '@trpc/client': 11.7.2 - '@trpc/server': 11.7.2 + '@trpc/client': 11.8.0 + '@trpc/server': 11.8.0 react: '>=18.2.0' react-dom: '>=18.2.0' typescript: '>=5.7.2' - '@trpc/server@11.7.2': - resolution: {integrity: sha512-AgB26PXY69sckherIhCacKLY49rxE2XP5h38vr/KMZTbLCL1p8IuIoKPjALTcugC2kbyQ7Lbqo2JDVfRSmPmfQ==} + '@trpc/server@11.8.0': + resolution: {integrity: sha512-DphyQnLuyX2nwJCQGWQ9zYz4hZGvRhSBqDhQ0SH3tDhQ3PU4u68xofA0pJ741Ir4InEAFD+TtJVLAQy+wVOkiQ==} peerDependencies: typescript: '>=5.7.2' - '@trpc/tanstack-react-query@11.7.2': - resolution: {integrity: sha512-3XrY0b8lV0Fhj4Z2hVn1d1ZJzq2/stbc2F1e9Y6RrUWOfLmOKHlEVHYO1QfDGM6rqj66DkUj7eA593hAI0VTkQ==} + '@trpc/tanstack-react-query@11.8.0': + resolution: {integrity: sha512-LtLVyYzv5MjviBUElkCn1X246BCcq2j58N7mNxg4IqaR9Uvw9y7q6VdJMo5jWO9XogBVltgvHc5sZCJ+7IE1NQ==} peerDependencies: '@tanstack/react-query': ^5.80.3 - '@trpc/client': 11.7.2 - '@trpc/server': 11.7.2 + '@trpc/client': 11.8.0 + '@trpc/server': 11.8.0 react: '>=18.2.0' react-dom: '>=18.2.0' typescript: '>=5.7.2' @@ -5082,14 +5014,14 @@ packages: '@types/node@22.19.3': resolution: {integrity: sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==} - '@types/node@24.10.1': - resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} + '@types/node@24.10.4': + resolution: {integrity: sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} - '@types/pg@8.15.6': - resolution: {integrity: sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==} + '@types/pg@8.16.0': + resolution: {integrity: sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ==} '@types/prismjs@1.26.5': resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==} @@ -5165,63 +5097,63 @@ packages: '@types/xml2js@0.4.14': resolution: {integrity: sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==} - '@typescript-eslint/eslint-plugin@8.48.1': - resolution: {integrity: sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA==} + '@typescript-eslint/eslint-plugin@8.49.0': + resolution: {integrity: sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.48.1 + '@typescript-eslint/parser': ^8.49.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.48.1': - resolution: {integrity: sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==} + '@typescript-eslint/parser@8.49.0': + resolution: {integrity: sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.48.1': - resolution: {integrity: sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w==} + '@typescript-eslint/project-service@8.49.0': + resolution: {integrity: sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.48.1': - resolution: {integrity: sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w==} + '@typescript-eslint/scope-manager@8.49.0': + resolution: {integrity: sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.48.1': - resolution: {integrity: sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw==} + '@typescript-eslint/tsconfig-utils@8.49.0': + resolution: {integrity: sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.48.1': - resolution: {integrity: sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg==} + '@typescript-eslint/type-utils@8.49.0': + resolution: {integrity: sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.48.1': - resolution: {integrity: sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==} + '@typescript-eslint/types@8.49.0': + resolution: {integrity: sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.48.1': - resolution: {integrity: sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==} + '@typescript-eslint/typescript-estree@8.49.0': + resolution: {integrity: sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.48.1': - resolution: {integrity: sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA==} + '@typescript-eslint/utils@8.49.0': + resolution: {integrity: sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.48.1': - resolution: {integrity: sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==} + '@typescript-eslint/visitor-keys@8.49.0': + resolution: {integrity: sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@umami/node@0.4.0': @@ -5261,11 +5193,11 @@ packages: '@videojs/xhr@2.7.0': resolution: {integrity: sha512-giab+EVRanChIupZK7gXjHy90y3nncA2phIOyG3Ne5fvpiMJzvqYwiTOnEVW2S4CoYcuKJkomat7bMXA/UoUZQ==} - '@vitejs/plugin-react@5.1.1': - resolution: {integrity: sha512-WQfkSw0QbQ5aJ2CHYw23ZGkqnRwqKHD/KYsMeTkZzPT4Jcf0DcBxBtwMJxnu6E7oxw5+JC6ZAiePgh28uJ1HBA==} + '@vitejs/plugin-react@5.1.2': + resolution: {integrity: sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: - vite: '>=7.2.6' + vite: '>=7.2.7' '@vitest/coverage-v8@4.0.15': resolution: {integrity: sha512-FUJ+1RkpTFW7rQITdgTi93qOCWJobWhBirEPCeXh2SW2wsTlFxy51apDz5gzG+ZEYt/THvWeNmhdAoS9DTwpCw==} @@ -6269,8 +6201,8 @@ packages: engines: {node: '>=4'} hasBin: true - cssstyle@5.3.3: - resolution: {integrity: sha512-OytmFH+13/QXONJcC75QNdMtKpceNk3u8ThBjyyYjkEcy/ekBwR1mMAuNvi3gdBPW3N5TlCzQ0WZw8H0lN/bDw==} + cssstyle@5.3.4: + resolution: {integrity: sha512-KyOS/kJMEq5O9GdPnaf82noigg5X5DYn0kZPJTaAsCUaBizp6Xa1y9D4Qoqf/JazEXWuruErHgVXwjN5391ZJw==} engines: {node: '>=20'} csstype@3.2.3: @@ -6517,8 +6449,8 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} - dns-caching@0.2.7: - resolution: {integrity: sha512-48fgJWH9aUHPN+TOmbozx1hxX9YoCsEkZzR/wHKRFPFx0NZtmSgtZY3M0Vsl7x/fDcq3mCI3SMgyTKVgZZrogg==} + dns-caching@0.2.9: + resolution: {integrity: sha512-FSvmI/dC/HXYIyX2m7MvQt36Yut49GCw3hWaDqaTgLLmKyLs5lmv/jhmNKqWjSF2vRGTgFYL0G3mwcjLlKXj0w==} docker-compose@1.3.0: resolution: {integrity: sha512-7Gevk/5eGD50+eMD+XDnFnOrruFkL0kSd7jEG4cjmqweDSUhB7i0g8is/nBdVpl+Bx338SqIB2GLKm32M+Vs6g==} @@ -6542,8 +6474,8 @@ packages: dom-walk@0.1.2: resolution: {integrity: sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==} - dompurify@3.3.0: - resolution: {integrity: sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==} + dompurify@3.3.1: + resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==} dot-case@2.1.1: resolution: {integrity: sha512-HnM6ZlFqcajLsyudHq7LeeLDr2rFAVYtDv/hV5qchQEidSck8j9OPUsXY9KwJv/lHMtYlX4DjRQqwFYa+0r8Ug==} @@ -6576,8 +6508,8 @@ packages: resolution: {integrity: sha512-O9EC/miwdnRDY10qRxM8P3Pg8hXe3LyU4ZipReKOgTwn4OqANmftj8XJz1UPUAS6NMHf0E2htjsbQujUTkncCg==} hasBin: true - drizzle-orm@0.45.0: - resolution: {integrity: sha512-lyd9VRk3SXKRjV/gQckQzmJgkoYMvVG3A2JAV0vh3L+Lwk+v9+rK5Gj0H22y+ZBmxsrRBgJ5/RbQCN7DWd1dtQ==} + drizzle-orm@0.45.1: + resolution: {integrity: sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' '@cloudflare/workers-types': '>=4' @@ -6951,8 +6883,8 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.39.1: - resolution: {integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==} + eslint@9.39.2: + resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -7060,6 +6992,10 @@ packages: resolution: {integrity: sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==} engines: {node: '>=6.0.0'} + fast-equals@5.3.3: + resolution: {integrity: sha512-/boTcHZeIAQ2r/tL11voclBHDeP9WPxLt+tyAbVSyyXuUFyh0Tne7gJZTqGbxnvj79TjLdCXLOY7UIPhyG5MTw==} + engines: {node: '>=6.0.0'} + fast-fifo@1.3.2: resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} @@ -7086,8 +7022,8 @@ packages: fast-uri@3.0.6: resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==} - fast-xml-parser@5.3.2: - resolution: {integrity: sha512-n8v8b6p4Z1sMgqRmqLJm3awW4NX7NkaKPfb3uJIBTSH7Pdvufi3PQ3/lJLQrvxcMYl7JI2jnDO90siPEpD8JBA==} + fast-xml-parser@5.3.3: + resolution: {integrity: sha512-2O3dkPAAC6JavuMm8+4+pgTk+5hoAs+CjZ+sWcQLkX9+/tHRuTkQh/Oaifr8qDmZ8iEHb771Ea6G8CdwkrgvYA==} hasBin: true faster-babel-types@0.1.0: @@ -7431,9 +7367,6 @@ packages: resolution: {integrity: sha512-rEDCuqUQ4tbD78TpzsMtt5OIf0cBCSDWSJtUDaF6JsAh+k0v9r++NzxNEG87oDZx9ZwGhD8DaezR2L/yrw0Jdw==} engines: {node: '>=10'} - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - h3@1.15.1: resolution: {integrity: sha512-+ORaOBttdUm1E2Uu/obAyCguiI7MbBvsLTndc3gyK3zU+SYLoZXlyCP9Xgy0gikkGufFLTZXCXD6+4BsufnmHA==} @@ -7557,8 +7490,8 @@ packages: highlightjs-curl@1.3.0: resolution: {integrity: sha512-50UEfZq1KR0Lfk2Tr6xb/MUIZH3h10oNC0OTy9g7WELcs5Fgy/mKN1vEhuKTkKbdo8vr5F9GXstu2eLhApfQ3A==} - hono@4.10.7: - resolution: {integrity: sha512-icXIITfw/07Q88nLSkB9aiUrd8rYzSweK681Kjo/TSggaGbOX4RRyxxm71v+3PC8C/j+4rlxGeoTRxQDkaJkUw==} + hono@4.11.1: + resolution: {integrity: sha512-KsFcH0xxHes0J4zaQgWbYwmz3UPOOskdqZmItstUG93+Wk1ePBLkLGwbP9zlmh1BFUiL8Qp+Xfu9P7feJWpGNg==} engines: {node: '>=16.9.0'} hook-std@4.0.0: @@ -8042,8 +7975,8 @@ packages: resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} engines: {node: '>=16'} - isomorphic-dompurify@2.33.0: - resolution: {integrity: sha512-pXGR3rAHAXb5Bvr56pc5aV0Lh8laubLo7/60F+soOzDFmwks6MtgDhL7p46VoxLnwgIsjgmVhQpUt4mUlL/XEw==} + isomorphic-dompurify@2.34.0: + resolution: {integrity: sha512-7VeB/tDBQ8jt1+syT563hmmejY01nuwizpUIFPfM1aw3iTgLLiVP4/Nh+PKhNoa1V/H+E6ZlNcowsXLbChPCpw==} engines: {node: '>=20.19.5'} isomorphic-fetch@3.0.0: @@ -8092,8 +8025,8 @@ packages: jose@6.0.8: resolution: {integrity: sha512-EyUPtOKyTYq+iMOszO42eobQllaIjJnwkZ2U93aJzNyPibCy7CEvT9UQnaCVB51IAd49gbNdCew1c0LcLTCB2g==} - jotai@2.15.2: - resolution: {integrity: sha512-El86CCfXNMEOytp20NPfppqGGmcp6H6kIA+tJHdmASEUURJCYW4fh8nTHEnB8rUXEFAY1pm8PdHPwnrcPGwdEg==} + jotai@2.16.0: + resolution: {integrity: sha512-NmkwPBet0SHQ28GBfEb10sqnbVOYyn6DL4iazZgGRDUKxSWL0iqcm+IK4TqTSFC2ixGk+XX2e46Wbv364a3cKg==} engines: {node: '>=12.20.0'} peerDependencies: '@babel/core': '>=7.0.0' @@ -8130,8 +8063,8 @@ packages: jsbn@1.1.0: resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} - jsdom@27.2.0: - resolution: {integrity: sha512-454TI39PeRDW1LgpyLPyURtB4Zx1tklSr6+OFOipsxGUH1WMTvk6C65JQdrj455+DP2uJ1+veBEHTGFKWVLFoA==} + jsdom@27.3.0: + resolution: {integrity: sha512-GtldT42B8+jefDUC4yUKAvsaOrH7PDHmZxZXNgF2xMmymjUbRYJvpAybZAKEmXDGTM0mCsz8duOa4vTm5AY2Kg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: canvas: ^3.0.0 @@ -8231,8 +8164,8 @@ packages: resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} engines: {node: '>= 0.6.3'} - ldapts@8.0.12: - resolution: {integrity: sha512-jk/OfBiXQFsYzvIxmgKwUzft94YMkVIOkf2P+qrrlLNgvWVP/F2TxYj5j6nLAyRtU/KVm87e15k9hMg1c3D25Q==} + ldapts@8.0.23: + resolution: {integrity: sha512-HjTGtXlSEAMniCI4z9nrf5YWLgq4Mc0XJJYliiLY79/IU8lgNX5gEMjzRGncpBJmVJooxI6xFSKzFbs1KuHL7w==} engines: {node: '>=20'} leven@4.1.0: @@ -8349,14 +8282,14 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.1.0: - resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==} - engines: {node: 20 || >=22} - lru-cache@11.2.2: resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==} engines: {node: 20 || >=22} + lru-cache@11.2.4: + resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -8748,11 +8681,11 @@ packages: nodemailer: optional: true - next-intl-swc-plugin-extractor@4.5.8: - resolution: {integrity: sha512-hscCKUv+5GQ0CCNbvqZ8gaxnAGToCgDTbL++jgCq8SCk/ljtZDEeQZcMk46Nm6Ynn49Q/JKF4Npo/Sq1mpbusA==} + next-intl-swc-plugin-extractor@4.6.0: + resolution: {integrity: sha512-H0Qd44LfvVWRidzmtDHicWPANrAsiLIR2jPwCVck3tNuf7H/F6g4JwucmR1TWrd/X6wOJe8pbYHRA9THhjc4vQ==} - next-intl@4.5.8: - resolution: {integrity: sha512-BdN6494nvt09WtmW5gbWdwRhDDHC/Sg7tBMhN7xfYds3vcRCngSDXat81gmJkblw9jYOv8zXzzFJyu5VYXnJzg==} + next-intl@4.6.0: + resolution: {integrity: sha512-Z2LUrIwqMt1/pnh7LwU0OUju17Q0eO1b75I2hAsVU5zZhOkxRP7dRKabgDPg4Hy4EoEx70jNqLVKll+BT1wz0w==} peerDependencies: next: ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0 @@ -8761,8 +8694,8 @@ packages: typescript: optional: true - next@16.0.7: - resolution: {integrity: sha512-3mBRJyPxT4LOxAJI6IsXeFtKfiJUbjCLgvXO02fV8Wy/lIhPvP94Fe7dGhUgHXcQy4sSuYwQNcOLhIfOm0rL0A==} + next@16.0.10: + resolution: {integrity: sha512-RtWh5PUgI+vxlV3HdR+IfWA1UUHu0+Ram/JBO4vWB54cVPentCD0e+lxyAYEsDTqGGMg7qpjhKh6dc6aW7W/sA==} engines: {node: '>=20.9.0'} deprecated: This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/security-update-2025-12-11 for more details. hasBin: true @@ -9311,8 +9244,8 @@ packages: engines: {node: '>=18'} hasBin: true - po-parser@1.0.2: - resolution: {integrity: sha512-yTIQL8PZy7V8c0psPoJUx7fayez+Mo/53MZgX9MPuPHx+Dt+sRPNuRbI+6Oqxnddhkd68x4Nlgon/zizL1Xg+w==} + po-parser@2.0.0: + resolution: {integrity: sha512-SZvoKi3PoI/hHa2V9je9CW7Xgxl4dvO74cvaa6tWShIHT51FkPxje6pt0gTJznJrU67ix91nDaQp2hUxkOYhKA==} possible-typed-array-names@1.0.0: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} @@ -9483,8 +9416,8 @@ packages: prosemirror-schema-basic@1.2.3: resolution: {integrity: sha512-h+H0OQwZVqMon1PNn0AG9cTfx513zgIG2DY00eJ00Yvgb3UD+GQ/VlWW5rcaxacpCGT1Yx8nuhwXk4+QbXUfJA==} - prosemirror-schema-list@1.4.1: - resolution: {integrity: sha512-jbDyaP/6AFfDfu70VzySsD75Om2t3sXTOdl5+31Wlxlg62td1haUpty/ybajSfJ1pkGadlOfwQq9kgW5IMo1Rg==} + prosemirror-schema-list@1.5.1: + resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==} prosemirror-state@1.4.3: resolution: {integrity: sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==} @@ -9502,8 +9435,8 @@ packages: prosemirror-transform@1.10.2: resolution: {integrity: sha512-2iUq0wv2iRoJO/zj5mv8uDUriOHWzXRnOTVgCzSXnktS/2iQRa3UUQwVlkBlYZFtygw6Nh1+X4mGqoYBINn5KQ==} - prosemirror-view@1.37.2: - resolution: {integrity: sha512-ApcyrfV/cRcaL65on7TQcfWElwLyOgIjnIynfAuV+fIdlpbSvSWRwfuPaH7T5mo4AbO/FID29qOtjiDIKGWyog==} + prosemirror-view@1.41.4: + resolution: {integrity: sha512-WkKgnyjNncri03Gjaz3IFWvCAE94XoiEgvtr0/r2Xw7R8/IjK3sKLSiDoCHWcsXSAinVaKlGRZDvMCsF1kbzjA==} proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} @@ -9572,10 +9505,10 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true - react-dom@19.2.1: - resolution: {integrity: sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==} + react-dom@19.2.3: + resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==} peerDependencies: - react: ^19.2.1 + react: ^19.2.3 react-dropzone@14.3.8: resolution: {integrity: sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug==} @@ -9588,8 +9521,8 @@ packages: peerDependencies: react: '>=16.13.1' - react-is@19.2.1: - resolution: {integrity: sha512-L7BnWgRbMwzMAubQcS7sXdPdNLmKlucPlopgAzx7FtYbksWZgEWiuYM5x9T6UqS2Ne0rsgQTq5kY2SGqpzUkYA==} + react-is@19.2.3: + resolution: {integrity: sha512-qJNJfu81ByyabuG7hPFEbXqNcWSU3+eVus+KJs+0ncpGfMyYdvSmxiJxbWR65lYi1I+/0HBcliO029gc4F+PnA==} react-markdown@10.1.0: resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} @@ -9665,8 +9598,8 @@ packages: react: '>=16.6.0' react-dom: '>=16.6.0' - react@19.2.1: - resolution: {integrity: sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==} + react@19.2.3: + resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} engines: {node: '>=0.10.0'} read-package-up@11.0.0: @@ -9927,8 +9860,8 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sass@1.94.2: - resolution: {integrity: sha512-N+7WK20/wOr7CzA2snJcUSSNTCzeCGUTFY3OgeQP3mZ1aj9NMQ0mSTXwlrnd89j33zzQJGqIN52GIOmYrfq46A==} + sass@1.96.0: + resolution: {integrity: sha512-8u4xqqUeugGNCYwr9ARNtQKTOj4KmYiJAVKXf2CTIivTCR51j96htbMKWDru8H5SaQWpyVgTfOF8Ylyf5pun1Q==} engines: {node: '>=14.0.0'} hasBin: true @@ -10445,8 +10378,8 @@ packages: engines: {node: '>=10'} hasBin: true - testcontainers@11.9.0: - resolution: {integrity: sha512-SQ6OqQUig7HcGVF72i+ZVIMvxPSpEz8cgC/B63ekqMzgf98DnveoBbOmqux/Wa5wQAQCt4mEPNMa/Jz7vMg9fQ==} + testcontainers@11.10.0: + resolution: {integrity: sha512-8hwK2EnrOZfrHPpDC7CPe03q7H8Vv8j3aXdcmFFyNV8dzpBzgZYmqyDtduJ8YQ5kbzj+A+jUXMQ6zI8B5U3z+g==} text-decoder@1.2.0: resolution: {integrity: sha512-n1yg1mOj9DNpk3NeZOx7T6jchTbyJS3i3cucbNN6FcdPriMZx7NsgrGpWWdWZZGxD7ES1XB+3uoqHMgOKaN+fg==} @@ -10502,9 +10435,6 @@ packages: resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} engines: {node: '>=14.0.0'} - tippy.js@6.3.7: - resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==} - title-case@2.1.1: resolution: {integrity: sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q==} @@ -10762,8 +10692,8 @@ packages: typedarray-to-buffer@3.1.5: resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} - typescript-eslint@8.48.1: - resolution: {integrity: sha512-FbOKN1fqNoXp1hIl5KYpObVrp0mCn+CLgn479nmu2IsRMrx2vyv74MmsBLVlhg8qVwNFGbXSp8fh1zp8pEoC2A==} + typescript-eslint@8.49.0: + resolution: {integrity: sha512-zRSVH1WXD0uXczCXw+nsdjGPUdx4dfrs5VQoHnUWmv1U3oNlAKv4FUNdLDhVUg+gYn+a5hUESqch//Rv5wVhrg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -10945,8 +10875,8 @@ packages: peerDependencies: react: '>=16.13' - use-intl@4.5.8: - resolution: {integrity: sha512-rWPV2Sirw55BQbA/7ndUBtsikh8WXwBrUkZJ1mD35+emj/ogPPqgCZdv1DdrEFK42AjF1g5w8d3x8govhqPH6Q==} + use-intl@4.6.0: + resolution: {integrity: sha512-eEV2y3jpwH0DqGuc7iCRikH4D7tBrN4gHQD8uSNT3KGQSNJ9erHnfUdLkuWTSO8dJGpru3jbOmT4Xev/eqh7lg==} peerDependencies: react: ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0 @@ -10990,10 +10920,6 @@ packages: resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} hasBin: true - uuid@13.0.0: - resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==} - hasBin: true - uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true @@ -11042,7 +10968,7 @@ packages: vite-tsconfig-paths@5.1.4: resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} peerDependencies: - vite: '>=7.2.6' + vite: '>=7.2.7' peerDependenciesMeta: vite: optional: true @@ -11260,8 +11186,8 @@ packages: resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==} engines: {node: '>= 12.0.0'} - winston@3.18.3: - resolution: {integrity: sha512-NoBZauFNNWENgsnC9YpgyYwOVrl2m58PpQ8lNHjV3kosGs7KJ7Npk9pCUE+WJlawVSe8mykWDKWFSVfs3QO9ww==} + winston@3.19.0: + resolution: {integrity: sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==} engines: {node: '>= 12.0.0'} word-wrap@1.2.5: @@ -11451,23 +11377,23 @@ packages: snapshots: - '@acemir/cssom@0.9.23': {} + '@acemir/cssom@0.9.28': {} - '@actions/core@1.11.1': + '@actions/core@2.0.1': dependencies: - '@actions/exec': 1.1.1 - '@actions/http-client': 2.2.3 + '@actions/exec': 2.0.0 + '@actions/http-client': 3.0.0 - '@actions/exec@1.1.1': + '@actions/exec@2.0.0': dependencies: - '@actions/io': 1.1.3 + '@actions/io': 2.0.0 - '@actions/http-client@2.2.3': + '@actions/http-client@3.0.0': dependencies: tunnel: 0.0.6 undici: 5.29.0 - '@actions/io@1.1.3': {} + '@actions/io@2.0.0': {} '@ampproject/remapping@2.3.0': dependencies: @@ -11484,21 +11410,21 @@ snapshots: '@ark/util@0.46.0': optional: true - '@asamuzakjp/css-color@4.0.4': + '@asamuzakjp/css-color@4.1.0': 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-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 - lru-cache: 11.2.2 + lru-cache: 11.2.4 - '@asamuzakjp/dom-selector@6.7.4': + '@asamuzakjp/dom-selector@6.7.6': dependencies: '@asamuzakjp/nwsapi': 2.3.9 bidi-js: 1.0.3 css-tree: 3.1.0 is-potential-custom-element-name: 1.0.1 - lru-cache: 11.2.2 + lru-cache: 11.2.4 '@asamuzakjp/nwsapi@2.3.9': {} @@ -11627,7 +11553,7 @@ snapshots: dependencies: '@babel/parser': 7.28.5 '@babel/types': 7.28.5 - '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.0.2 @@ -11651,14 +11577,14 @@ snapshots: '@babel/helper-module-imports@7.25.9': dependencies: - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color '@babel/helper-module-imports@7.27.1': dependencies: - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color @@ -12031,36 +11957,36 @@ snapshots: enabled: 2.0.0 kuler: 2.0.0 - '@dnd-kit/accessibility@3.1.1(react@19.2.1)': + '@dnd-kit/accessibility@3.1.1(react@19.2.3)': dependencies: - react: 19.2.1 + react: 19.2.3 tslib: 2.8.1 - '@dnd-kit/core@6.3.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@dnd-kit/core@6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@dnd-kit/accessibility': 3.1.1(react@19.2.1) - '@dnd-kit/utilities': 3.2.2(react@19.2.1) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + '@dnd-kit/accessibility': 3.1.1(react@19.2.3) + '@dnd-kit/utilities': 3.2.2(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) tslib: 2.8.1 - '@dnd-kit/modifiers@9.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1)': + '@dnd-kit/modifiers@9.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)': dependencies: - '@dnd-kit/core': 6.3.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@dnd-kit/utilities': 3.2.2(react@19.2.1) - react: 19.2.1 + '@dnd-kit/core': 6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@dnd-kit/utilities': 3.2.2(react@19.2.3) + react: 19.2.3 tslib: 2.8.1 - '@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1)': + '@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)': dependencies: - '@dnd-kit/core': 6.3.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@dnd-kit/utilities': 3.2.2(react@19.2.1) - react: 19.2.1 + '@dnd-kit/core': 6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@dnd-kit/utilities': 3.2.2(react@19.2.3) + react: 19.2.3 tslib: 2.8.1 - '@dnd-kit/utilities@3.2.2(react@19.2.1)': + '@dnd-kit/utilities@3.2.2(react@19.2.3)': dependencies: - react: 19.2.1 + react: 19.2.3 tslib: 2.8.1 '@drizzle-team/brocli@0.10.2': {} @@ -12240,9 +12166,9 @@ snapshots: '@esbuild/win32-x64@0.27.1': optional: true - '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1)': + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.2)': dependencies: - eslint: 9.39.1 + eslint: 9.39.2 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} @@ -12271,13 +12197,13 @@ snapshots: globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.0 - js-yaml: 4.1.0 + js-yaml: 4.1.1 minimatch: 3.1.2 strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color - '@eslint/js@9.39.1': {} + '@eslint/js@9.39.2': {} '@eslint/object-schema@2.1.7': {} @@ -12290,7 +12216,7 @@ snapshots: dependencies: '@ndaidong/bellajs': 12.0.1 cross-fetch: 4.1.0 - fast-xml-parser: 5.3.2 + fast-xml-parser: 5.3.3 html-entities: 2.6.0 transitivePeerDependencies: - encoding @@ -12329,18 +12255,18 @@ snapshots: '@floating-ui/core': 1.7.3 '@floating-ui/utils': 0.2.10 - '@floating-ui/react-dom@2.1.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@floating-ui/react-dom@2.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@floating-ui/dom': 1.7.4 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@floating-ui/react@0.27.16(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@floating-ui/react@0.27.16(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@floating-ui/react-dom': 2.1.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@floating-ui/react-dom': 2.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@floating-ui/utils': 0.2.10 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) tabbable: 6.2.0 '@floating-ui/utils@0.2.10': {} @@ -12435,9 +12361,9 @@ snapshots: - undici - utf-8-validate - '@hono/node-server@1.13.0(hono@4.10.7)': + '@hono/node-server@1.13.0(hono@4.11.1)': dependencies: - hono: 4.10.7 + hono: 4.11.1 '@humanfs/core@0.19.1': {} @@ -12656,7 +12582,7 @@ snapshots: '@kubernetes/client-node@1.4.0': dependencies: '@types/js-yaml': 4.0.9 - '@types/node': 24.10.1 + '@types/node': 24.10.4 '@types/node-fetch': 2.6.13 '@types/stream-buffers': 3.0.7 form-data: 4.0.5 @@ -12735,95 +12661,95 @@ snapshots: js-base64: 3.7.8 optional: true - '@mantine/charts@8.3.9(@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/hooks@8.3.9(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(recharts@2.15.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1))': + '@mantine/charts@8.3.10(@mantine/core@8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/hooks@8.3.10(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(recharts@2.15.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3))': dependencies: - '@mantine/core': 8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@mantine/hooks': 8.3.9(react@19.2.1) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - recharts: 2.15.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@mantine/core': 8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@mantine/hooks': 8.3.10(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + recharts: 2.15.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@mantine/colors-generator@8.3.9(chroma-js@3.2.0)': + '@mantine/colors-generator@8.3.10(chroma-js@3.2.0)': dependencies: chroma-js: 3.2.0 - '@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@mantine/core@8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@floating-ui/react': 0.27.16(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@mantine/hooks': 8.3.9(react@19.2.1) + '@floating-ui/react': 0.27.16(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@mantine/hooks': 8.3.10(react@19.2.3) clsx: 2.1.1 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - react-number-format: 5.4.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - react-remove-scroll: 2.7.1(@types/react@19.2.7)(react@19.2.1) - react-textarea-autosize: 8.5.9(@types/react@19.2.7)(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + react-number-format: 5.4.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react-remove-scroll: 2.7.1(@types/react@19.2.7)(react@19.2.3) + react-textarea-autosize: 8.5.9(@types/react@19.2.7)(react@19.2.3) type-fest: 4.41.0 transitivePeerDependencies: - '@types/react' - '@mantine/dates@8.3.9(@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/hooks@8.3.9(react@19.2.1))(dayjs@1.11.19)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@mantine/dates@8.3.10(@mantine/core@8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/hooks@8.3.10(react@19.2.3))(dayjs@1.11.19)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@mantine/core': 8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@mantine/hooks': 8.3.9(react@19.2.1) + '@mantine/core': 8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@mantine/hooks': 8.3.10(react@19.2.3) clsx: 2.1.1 dayjs: 1.11.19 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@mantine/dropzone@8.3.9(@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/hooks@8.3.9(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@mantine/dropzone@8.3.10(@mantine/core@8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/hooks@8.3.10(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@mantine/core': 8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@mantine/hooks': 8.3.9(react@19.2.1) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - react-dropzone: 14.3.8(react@19.2.1) + '@mantine/core': 8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@mantine/hooks': 8.3.10(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + react-dropzone: 14.3.8(react@19.2.3) - '@mantine/form@8.3.9(react@19.2.1)': + '@mantine/form@8.3.10(react@19.2.3)': dependencies: fast-deep-equal: 3.1.3 klona: 2.0.6 - react: 19.2.1 + react: 19.2.3 - '@mantine/hooks@8.3.9(react@19.2.1)': + '@mantine/hooks@8.3.10(react@19.2.3)': dependencies: - react: 19.2.1 + react: 19.2.3 - '@mantine/modals@8.3.9(@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/hooks@8.3.9(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@mantine/modals@8.3.10(@mantine/core@8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/hooks@8.3.10(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@mantine/core': 8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@mantine/hooks': 8.3.9(react@19.2.1) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + '@mantine/core': 8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@mantine/hooks': 8.3.10(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@mantine/notifications@8.3.9(@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/hooks@8.3.9(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@mantine/notifications@8.3.10(@mantine/core@8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/hooks@8.3.10(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@mantine/core': 8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@mantine/hooks': 8.3.9(react@19.2.1) - '@mantine/store': 8.3.9(react@19.2.1) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - react-transition-group: 4.4.5(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@mantine/core': 8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@mantine/hooks': 8.3.10(react@19.2.3) + '@mantine/store': 8.3.10(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + react-transition-group: 4.4.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@mantine/spotlight@8.3.9(@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/hooks@8.3.9(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@mantine/spotlight@8.3.10(@mantine/core@8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/hooks@8.3.10(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@mantine/core': 8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@mantine/hooks': 8.3.9(react@19.2.1) - '@mantine/store': 8.3.9(react@19.2.1) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + '@mantine/core': 8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@mantine/hooks': 8.3.10(react@19.2.3) + '@mantine/store': 8.3.10(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@mantine/store@8.3.9(react@19.2.1)': + '@mantine/store@8.3.10(react@19.2.3)': dependencies: - react: 19.2.1 + react: 19.2.3 - '@mantine/tiptap@8.3.9(@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/hooks@8.3.9(react@19.2.1))(@tiptap/extension-link@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1))(@tiptap/react@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@mantine/tiptap@8.3.10(@mantine/core@8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/hooks@8.3.10(react@19.2.3))(@tiptap/extension-link@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))(@tiptap/react@3.13.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@mantine/core': 8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@mantine/hooks': 8.3.9(react@19.2.1) - '@tiptap/extension-link': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) - '@tiptap/react': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + '@mantine/core': 8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@mantine/hooks': 8.3.10(react@19.2.3) + '@tiptap/extension-link': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0) + '@tiptap/react': 3.13.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) '@marijn/find-cluster-break@1.0.2': {} @@ -12847,7 +12773,7 @@ snapshots: '@axiomhq/js': 1.0.0-rc.3 '@babel/core': 7.26.0 '@babel/types': 7.26.0 - '@hono/node-server': 1.13.0(hono@4.10.7) + '@hono/node-server': 1.13.0(hono@4.11.1) '@million/install': 1.0.14 '@rollup/pluginutils': 5.1.0 '@rrweb/types': 2.0.0-alpha.16 @@ -12855,7 +12781,7 @@ snapshots: ci-info: 4.0.0 esbuild: 0.27.1 faster-babel-types: 0.1.0(@babel/types@7.26.0) - hono: 4.10.7 + hono: 4.11.1 isomorphic-fetch: 3.0.0 nanoid: 5.1.6 ohash: 1.1.4 @@ -12882,34 +12808,34 @@ snapshots: '@ndaidong/bellajs@12.0.1': {} - '@next/env@16.0.7': {} + '@next/env@16.0.10': {} - '@next/eslint-plugin-next@16.0.7': + '@next/eslint-plugin-next@16.0.10': dependencies: fast-glob: 3.3.1 - '@next/swc-darwin-arm64@16.0.7': + '@next/swc-darwin-arm64@16.0.10': optional: true - '@next/swc-darwin-x64@16.0.7': + '@next/swc-darwin-x64@16.0.10': optional: true - '@next/swc-linux-arm64-gnu@16.0.7': + '@next/swc-linux-arm64-gnu@16.0.10': optional: true - '@next/swc-linux-arm64-musl@16.0.7': + '@next/swc-linux-arm64-musl@16.0.10': optional: true - '@next/swc-linux-x64-gnu@16.0.7': + '@next/swc-linux-x64-gnu@16.0.10': optional: true - '@next/swc-linux-x64-musl@16.0.7': + '@next/swc-linux-x64-musl@16.0.10': optional: true - '@next/swc-win32-arm64-msvc@16.0.7': + '@next/swc-win32-arm64-msvc@16.0.10': optional: true - '@next/swc-win32-x64-msvc@16.0.7': + '@next/swc-win32-x64-msvc@16.0.10': optional: true '@noble/hashes@2.0.1': {} @@ -13147,7 +13073,6 @@ snapshots: '@parcel/watcher-win32-arm64': 2.4.1 '@parcel/watcher-win32-ia32': 2.4.1 '@parcel/watcher-win32-x64': 2.4.1 - optional: true '@petamoriken/float16@3.9.3': optional: true @@ -13175,8 +13100,6 @@ snapshots: '@polka/url@1.0.0-next.25': {} - '@popperjs/core@2.11.8': {} - '@protobufjs/aspromise@1.1.2': {} '@protobufjs/base64@1.1.2': {} @@ -13208,7 +13131,7 @@ snapshots: '@codemirror/state': 6.5.2 '@codemirror/view': 6.39.4 - '@rolldown/pluginutils@1.0.0-beta.47': {} + '@rolldown/pluginutils@1.0.0-beta.53': {} '@rollup/pluginutils@5.1.0': dependencies: @@ -13348,11 +13271,11 @@ snapshots: - typescript - universal-cookie - '@scalar/api-reference-react@0.8.11(react@19.2.1)(tailwindcss@4.1.18)(typescript@5.9.3)': + '@scalar/api-reference-react@0.8.11(react@19.2.3)(tailwindcss@4.1.18)(typescript@5.9.3)': dependencies: '@scalar/api-reference': 1.40.5(tailwindcss@4.1.18)(typescript@5.9.3) '@scalar/types': 0.5.2 - react: 19.2.1 + react: 19.2.3 transitivePeerDependencies: - '@vue/composition-api' - async-validator @@ -13701,9 +13624,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@semantic-release/npm@13.1.2(semantic-release@25.0.2(typescript@5.9.3))': + '@semantic-release/npm@13.1.3(semantic-release@25.0.2(typescript@5.9.3))': dependencies: - '@actions/core': 1.11.1 + '@actions/core': 2.0.1 '@semantic-release/error': 4.0.0 aggregate-error: 5.0.0 env-ci: 11.2.0 @@ -13819,10 +13742,10 @@ snapshots: typescript: 5.9.3 zod: 4.1.13 - '@tabler/icons-react@3.35.0(react@19.2.1)': + '@tabler/icons-react@3.35.0(react@19.2.3)': dependencies: '@tabler/icons': 3.35.0 - react: 19.2.1 + react: 19.2.3 '@tabler/icons@3.35.0': {} @@ -13834,34 +13757,34 @@ snapshots: '@tanstack/query-devtools@5.91.1': {} - '@tanstack/react-query-devtools@5.91.1(@tanstack/react-query@5.90.12(react@19.2.1))(react@19.2.1)': + '@tanstack/react-query-devtools@5.91.1(@tanstack/react-query@5.90.12(react@19.2.3))(react@19.2.3)': dependencies: '@tanstack/query-devtools': 5.91.1 - '@tanstack/react-query': 5.90.12(react@19.2.1) - react: 19.2.1 + '@tanstack/react-query': 5.90.12(react@19.2.3) + react: 19.2.3 - '@tanstack/react-query-next-experimental@5.91.0(@tanstack/react-query@5.90.12(react@19.2.1))(next@16.0.7(@babel/core@7.26.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2))(react@19.2.1)': + '@tanstack/react-query-next-experimental@5.91.0(@tanstack/react-query@5.90.12(react@19.2.3))(next@16.0.10(@babel/core@7.26.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.96.0))(react@19.2.3)': dependencies: - '@tanstack/react-query': 5.90.12(react@19.2.1) - next: 16.0.7(@babel/core@7.26.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2) - react: 19.2.1 + '@tanstack/react-query': 5.90.12(react@19.2.3) + next: 16.0.10(@babel/core@7.26.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.96.0) + react: 19.2.3 - '@tanstack/react-query@5.90.12(react@19.2.1)': + '@tanstack/react-query@5.90.12(react@19.2.3)': dependencies: '@tanstack/query-core': 5.90.12 - react: 19.2.1 + react: 19.2.3 - '@tanstack/react-table@8.20.5(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@tanstack/react-table@8.20.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@tanstack/table-core': 8.20.5 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@tanstack/react-virtual@3.11.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@tanstack/react-virtual@3.11.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@tanstack/virtual-core': 3.11.2 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) '@tanstack/table-core@8.20.5': {} @@ -13874,183 +13797,189 @@ snapshots: '@tanstack/virtual-core': 3.13.13 vue: 3.5.25(typescript@5.9.3) - '@testcontainers/mysql@11.9.0': + '@testcontainers/mysql@11.10.0': dependencies: - testcontainers: 11.9.0 + testcontainers: 11.10.0 transitivePeerDependencies: - bare-buffer - supports-color - '@testcontainers/postgresql@11.9.0': + '@testcontainers/postgresql@11.10.0': dependencies: - testcontainers: 11.9.0 + testcontainers: 11.10.0 transitivePeerDependencies: - bare-buffer - supports-color - '@testcontainers/redis@11.9.0': + '@testcontainers/redis@11.10.0': dependencies: - testcontainers: 11.9.0 + testcontainers: 11.10.0 transitivePeerDependencies: - bare-buffer - supports-color - '@tiptap/core@2.27.1(@tiptap/pm@2.27.1)': + '@tiptap/core@3.13.0(@tiptap/pm@3.13.0)': dependencies: - '@tiptap/pm': 2.27.1 + '@tiptap/pm': 3.13.0 - '@tiptap/extension-blockquote@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-blockquote@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0) - '@tiptap/extension-bold@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-bold@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0) - '@tiptap/extension-bubble-menu@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)': + '@tiptap/extension-bubble-menu@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 - tippy.js: 6.3.7 + '@floating-ui/dom': 1.7.4 + '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0) + '@tiptap/pm': 3.13.0 + optional: true - '@tiptap/extension-bullet-list@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-bullet-list@3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/extension-list': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0) - '@tiptap/extension-code-block@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)': + '@tiptap/extension-code-block@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 + '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0) + '@tiptap/pm': 3.13.0 - '@tiptap/extension-code@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-code@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0) - '@tiptap/extension-color@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/extension-text-style@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)))': + '@tiptap/extension-color@3.13.0(@tiptap/extension-text-style@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0)))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/extension-text-style': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) + '@tiptap/extension-text-style': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0)) - '@tiptap/extension-document@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-document@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0) - '@tiptap/extension-dropcursor@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)': + '@tiptap/extension-dropcursor@3.13.0(@tiptap/extensions@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 + '@tiptap/extensions': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0) - '@tiptap/extension-floating-menu@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)': + '@tiptap/extension-floating-menu@3.13.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 - tippy.js: 6.3.7 + '@floating-ui/dom': 1.7.4 + '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0) + '@tiptap/pm': 3.13.0 + optional: true - '@tiptap/extension-gapcursor@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)': + '@tiptap/extension-gapcursor@3.13.0(@tiptap/extensions@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 + '@tiptap/extensions': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0) - '@tiptap/extension-hard-break@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-hard-break@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0) - '@tiptap/extension-heading@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-heading@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0) - '@tiptap/extension-highlight@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-highlight@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0) - '@tiptap/extension-history@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)': + '@tiptap/extension-horizontal-rule@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 + '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0) + '@tiptap/pm': 3.13.0 - '@tiptap/extension-horizontal-rule@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)': + '@tiptap/extension-image@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 + '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0) - '@tiptap/extension-image@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-italic@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0) - '@tiptap/extension-italic@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-link@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - - '@tiptap/extension-link@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)': - dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 + '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0) + '@tiptap/pm': 3.13.0 linkifyjs: 4.3.2 - '@tiptap/extension-list-item@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-list-item@3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/extension-list': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0) - '@tiptap/extension-ordered-list@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-list-keymap@3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/extension-list': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0) - '@tiptap/extension-paragraph@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0) + '@tiptap/pm': 3.13.0 - '@tiptap/extension-placeholder@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)': + '@tiptap/extension-ordered-list@3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 + '@tiptap/extension-list': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0) - '@tiptap/extension-strike@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-paragraph@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0) - '@tiptap/extension-table-cell@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-placeholder@3.13.0(@tiptap/extensions@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/extensions': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0) - '@tiptap/extension-table-header@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-strike@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0) - '@tiptap/extension-table-row@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-table-cell@3.13.0(@tiptap/extension-table@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/extension-table': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0) - '@tiptap/extension-table@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)': + '@tiptap/extension-table-header@3.13.0(@tiptap/extension-table@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 + '@tiptap/extension-table': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0) - '@tiptap/extension-task-item@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)': + '@tiptap/extension-table-row@3.13.0(@tiptap/extension-table@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 + '@tiptap/extension-table': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0) - '@tiptap/extension-task-list@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-table@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0) + '@tiptap/pm': 3.13.0 - '@tiptap/extension-text-align@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-task-item@3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/extension-list': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0) - '@tiptap/extension-text-style@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-task-list@3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/extension-list': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0) - '@tiptap/extension-text@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-text-align@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0) - '@tiptap/extension-underline@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + '@tiptap/extension-text-style@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0) - '@tiptap/pm@2.27.1': + '@tiptap/extension-text@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))': + dependencies: + '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0) + + '@tiptap/extension-underline@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))': + dependencies: + '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0) + + '@tiptap/extensions@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)': + dependencies: + '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0) + '@tiptap/pm': 3.13.0 + + '@tiptap/pm@3.13.0': dependencies: prosemirror-changeset: 2.3.0 prosemirror-collab: 1.3.1 @@ -14064,88 +13993,96 @@ snapshots: prosemirror-menu: 1.2.4 prosemirror-model: 1.24.1 prosemirror-schema-basic: 1.2.3 - prosemirror-schema-list: 1.4.1 + prosemirror-schema-list: 1.5.1 prosemirror-state: 1.4.3 prosemirror-tables: 1.6.4 - prosemirror-trailing-node: 3.0.0(prosemirror-model@1.24.1)(prosemirror-state@1.4.3)(prosemirror-view@1.37.2) + prosemirror-trailing-node: 3.0.0(prosemirror-model@1.24.1)(prosemirror-state@1.4.3)(prosemirror-view@1.41.4) prosemirror-transform: 1.10.2 - prosemirror-view: 1.37.2 + prosemirror-view: 1.41.4 - '@tiptap/react@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@tiptap/react@3.13.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/extension-bubble-menu': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) - '@tiptap/extension-floating-menu': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) - '@tiptap/pm': 2.27.1 + '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0) + '@tiptap/pm': 3.13.0 + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) '@types/use-sync-external-store': 0.0.6 - fast-deep-equal: 3.1.3 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - use-sync-external-store: 1.4.0(react@19.2.1) + fast-equals: 5.3.3 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + use-sync-external-store: 1.4.0(react@19.2.3) + optionalDependencies: + '@tiptap/extension-bubble-menu': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0) + '@tiptap/extension-floating-menu': 3.13.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0) + transitivePeerDependencies: + - '@floating-ui/dom' - '@tiptap/starter-kit@2.27.1': + '@tiptap/starter-kit@3.13.0': dependencies: - '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) - '@tiptap/extension-blockquote': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-bold': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-bullet-list': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-code': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-code-block': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) - '@tiptap/extension-document': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-dropcursor': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) - '@tiptap/extension-gapcursor': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) - '@tiptap/extension-hard-break': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-heading': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-history': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) - '@tiptap/extension-horizontal-rule': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) - '@tiptap/extension-italic': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-list-item': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-ordered-list': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-paragraph': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-strike': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-text': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/extension-text-style': 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) - '@tiptap/pm': 2.27.1 + '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0) + '@tiptap/extension-blockquote': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0)) + '@tiptap/extension-bold': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0)) + '@tiptap/extension-bullet-list': 3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)) + '@tiptap/extension-code': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0)) + '@tiptap/extension-code-block': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0) + '@tiptap/extension-document': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0)) + '@tiptap/extension-dropcursor': 3.13.0(@tiptap/extensions@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)) + '@tiptap/extension-gapcursor': 3.13.0(@tiptap/extensions@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)) + '@tiptap/extension-hard-break': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0)) + '@tiptap/extension-heading': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0)) + '@tiptap/extension-horizontal-rule': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0) + '@tiptap/extension-italic': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0)) + '@tiptap/extension-link': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0) + '@tiptap/extension-list': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0) + '@tiptap/extension-list-item': 3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)) + '@tiptap/extension-list-keymap': 3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)) + '@tiptap/extension-ordered-list': 3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)) + '@tiptap/extension-paragraph': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0)) + '@tiptap/extension-strike': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0)) + '@tiptap/extension-text': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0)) + '@tiptap/extension-underline': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0)) + '@tiptap/extensions': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0) + '@tiptap/pm': 3.13.0 '@tootallnate/quickjs-emscripten@0.23.0': {} - '@trpc/client@11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3)': + '@trpc/client@11.8.0(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3)': dependencies: - '@trpc/server': 11.7.2(typescript@5.9.3) + '@trpc/server': 11.8.0(typescript@5.9.3) typescript: 5.9.3 - '@trpc/next@11.7.2(@tanstack/react-query@5.90.12(react@19.2.1))(@trpc/client@11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3))(@trpc/react-query@11.7.2(@tanstack/react-query@5.90.12(react@19.2.1))(@trpc/client@11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.7.2(typescript@5.9.3))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3))(@trpc/server@11.7.2(typescript@5.9.3))(next@16.0.7(@babel/core@7.26.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3)': + '@trpc/next@11.8.0(@tanstack/react-query@5.90.12(react@19.2.3))(@trpc/client@11.8.0(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/react-query@11.8.0(@tanstack/react-query@5.90.12(react@19.2.3))(@trpc/client@11.8.0(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.8.0(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(@trpc/server@11.8.0(typescript@5.9.3))(next@16.0.10(@babel/core@7.26.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.96.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)': dependencies: - '@trpc/client': 11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3) - '@trpc/server': 11.7.2(typescript@5.9.3) - next: 16.0.7(@babel/core@7.26.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + '@trpc/client': 11.8.0(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3) + '@trpc/server': 11.8.0(typescript@5.9.3) + next: 16.0.10(@babel/core@7.26.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.96.0) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) typescript: 5.9.3 optionalDependencies: - '@tanstack/react-query': 5.90.12(react@19.2.1) - '@trpc/react-query': 11.7.2(@tanstack/react-query@5.90.12(react@19.2.1))(@trpc/client@11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.7.2(typescript@5.9.3))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + '@tanstack/react-query': 5.90.12(react@19.2.3) + '@trpc/react-query': 11.8.0(@tanstack/react-query@5.90.12(react@19.2.3))(@trpc/client@11.8.0(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.8.0(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@trpc/react-query@11.7.2(@tanstack/react-query@5.90.12(react@19.2.1))(@trpc/client@11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.7.2(typescript@5.9.3))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3)': + '@trpc/react-query@11.8.0(@tanstack/react-query@5.90.12(react@19.2.3))(@trpc/client@11.8.0(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.8.0(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)': dependencies: - '@tanstack/react-query': 5.90.12(react@19.2.1) - '@trpc/client': 11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3) - '@trpc/server': 11.7.2(typescript@5.9.3) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + '@tanstack/react-query': 5.90.12(react@19.2.3) + '@trpc/client': 11.8.0(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3) + '@trpc/server': 11.8.0(typescript@5.9.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) typescript: 5.9.3 - '@trpc/server@11.7.2(typescript@5.9.3)': + '@trpc/server@11.8.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@trpc/tanstack-react-query@11.7.2(@tanstack/react-query@5.90.12(react@19.2.1))(@trpc/client@11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.7.2(typescript@5.9.3))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3)': + '@trpc/tanstack-react-query@11.8.0(@tanstack/react-query@5.90.12(react@19.2.3))(@trpc/client@11.8.0(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.8.0(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)': dependencies: - '@tanstack/react-query': 5.90.12(react@19.2.1) - '@trpc/client': 11.7.2(@trpc/server@11.7.2(typescript@5.9.3))(typescript@5.9.3) - '@trpc/server': 11.7.2(typescript@5.9.3) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + '@tanstack/react-query': 5.90.12(react@19.2.3) + '@trpc/client': 11.8.0(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3) + '@trpc/server': 11.8.0(typescript@5.9.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) typescript: 5.9.3 '@tsconfig/node10@1.0.11': {} @@ -14158,7 +14095,7 @@ snapshots: '@tsconfig/svelte@1.0.13': {} - '@turbo/gen@2.6.3(@swc/core@1.15.3)(@types/node@24.10.1)(typescript@5.9.3)': + '@turbo/gen@2.6.3(@swc/core@1.15.3)(@types/node@24.10.4)(typescript@5.9.3)': dependencies: '@turbo/workspaces': 2.6.3 commander: 10.0.1 @@ -14168,7 +14105,7 @@ snapshots: node-plop: 0.26.3 picocolors: 1.0.1 proxy-agent: 6.5.0 - ts-node: 10.9.2(@swc/core@1.15.3)(@types/node@24.10.1)(typescript@5.9.3) + ts-node: 10.9.2(@swc/core@1.15.3)(@types/node@24.10.4)(typescript@5.9.3) update-check: 1.5.4 validate-npm-package-name: 5.0.1 transitivePeerDependencies: @@ -14194,11 +14131,11 @@ snapshots: '@types/adm-zip@0.5.7': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.4 '@types/asn1@0.2.4': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.4 '@types/aws-lambda@8.10.146': {} @@ -14225,16 +14162,16 @@ snapshots: '@types/bcrypt@6.0.0': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.4 '@types/better-sqlite3@7.6.13': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.4 '@types/body-parser@1.19.5': dependencies: '@types/connect': 3.4.38 - '@types/node': 24.10.1 + '@types/node': 24.10.4 '@types/chai@5.2.2': dependencies: @@ -14244,7 +14181,7 @@ snapshots: '@types/connect@3.4.38': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.4 '@types/cookie@0.4.1': {} @@ -14253,11 +14190,11 @@ snapshots: '@types/connect': 3.4.38 '@types/express': 4.17.21 '@types/keygrip': 1.0.6 - '@types/node': 24.10.1 + '@types/node': 24.10.4 '@types/cors@2.8.17': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.4 '@types/css-font-loading-module@0.0.7': {} @@ -14295,13 +14232,13 @@ snapshots: '@types/docker-modem@3.0.6': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.4 '@types/ssh2': 1.15.1 '@types/dockerode@3.3.47': dependencies: '@types/docker-modem': 3.0.6 - '@types/node': 24.10.1 + '@types/node': 24.10.4 '@types/ssh2': 1.15.1 '@types/estree-jsx@1.0.5': @@ -14312,7 +14249,7 @@ snapshots: '@types/express-serve-static-core@4.19.5': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.4 '@types/qs': 6.9.16 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 @@ -14327,7 +14264,7 @@ snapshots: '@types/glob@7.2.0': dependencies: '@types/minimatch': 5.1.2 - '@types/node': 24.10.1 + '@types/node': 24.10.4 '@types/har-format@1.2.16': {} @@ -14373,7 +14310,7 @@ snapshots: '@types/node-fetch@2.6.13': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.4 form-data: 4.0.5 '@types/node-unifi@2.5.1(patch_hash=5e6ae51e2a17a7f9729bfa30b0eb3d0842a5810ac6db47603ab4a6efa1ed84c5)': @@ -14388,15 +14325,15 @@ snapshots: dependencies: undici-types: 6.21.0 - '@types/node@24.10.1': + '@types/node@24.10.4': dependencies: undici-types: 7.16.0 '@types/normalize-package-data@2.4.4': {} - '@types/pg@8.15.6': + '@types/pg@8.16.0': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.4 pg-protocol: 1.10.3 pg-types: 2.2.0 @@ -14419,21 +14356,21 @@ snapshots: '@types/send@0.17.4': dependencies: '@types/mime': 1.3.5 - '@types/node': 24.10.1 + '@types/node': 24.10.4 '@types/serve-static@1.15.7': dependencies: '@types/http-errors': 2.0.4 - '@types/node': 24.10.1 + '@types/node': 24.10.4 '@types/send': 0.17.4 '@types/ssh2-streams@0.1.12': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.4 '@types/ssh2@0.5.52': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.4 '@types/ssh2-streams': 0.1.12 '@types/ssh2@1.15.1': @@ -14442,11 +14379,11 @@ snapshots: '@types/stream-buffers@3.0.7': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.4 '@types/through@0.0.33': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.4 '@types/tinycolor2@1.4.6': {} @@ -14469,22 +14406,21 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.4 '@types/xml2js@0.4.14': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.4 - '@typescript-eslint/eslint-plugin@8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.48.1(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.48.1 - '@typescript-eslint/type-utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.48.1 - eslint: 9.39.1 - graphemer: 1.4.0 + '@typescript-eslint/parser': 8.49.0(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.49.0 + eslint: 9.39.2 ignore: 7.0.4 natural-compare: 1.4.0 ts-api-utils: 2.1.0(typescript@5.9.3) @@ -14492,56 +14428,56 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/parser@8.49.0(eslint@9.39.2)(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.48.1 - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.48.1 + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.49.0 debug: 4.4.3 - eslint: 9.39.1 + eslint: 9.39.2 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.48.1(typescript@5.9.3)': + '@typescript-eslint/project-service@8.49.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.48.1(typescript@5.9.3) - '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) + '@typescript-eslint/types': 8.49.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.48.1': + '@typescript-eslint/scope-manager@8.49.0': dependencies: - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/visitor-keys': 8.48.1 + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/visitor-keys': 8.49.0 - '@typescript-eslint/tsconfig-utils@8.48.1(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.49.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.48.1(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.49.0(eslint@9.39.2)(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.2)(typescript@5.9.3) debug: 4.4.3 - eslint: 9.39.1 + eslint: 9.39.2 ts-api-utils: 2.1.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.48.1': {} + '@typescript-eslint/types@8.49.0': {} - '@typescript-eslint/typescript-estree@8.48.1(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.49.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.48.1(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.48.1(typescript@5.9.3) - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/visitor-keys': 8.48.1 + '@typescript-eslint/project-service': 8.49.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/visitor-keys': 8.49.0 debug: 4.4.3 minimatch: 9.0.5 semver: 7.7.3 @@ -14551,20 +14487,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.48.1(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/utils@8.49.0(eslint@9.39.2)(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) - '@typescript-eslint/scope-manager': 8.48.1 - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) - eslint: 9.39.1 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2) + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) + eslint: 9.39.2 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.48.1': + '@typescript-eslint/visitor-keys@8.49.0': dependencies: - '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/types': 8.49.0 eslint-visitor-keys: 4.2.1 '@umami/node@0.4.0': {} @@ -14622,15 +14558,15 @@ snapshots: global: 4.4.0 is-function: 1.0.2 - '@vitejs/plugin-react@5.1.1(vite@7.1.12(@types/node@24.10.1)(sass@1.94.2)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2))': + '@vitejs/plugin-react@5.1.2(vite@7.1.12(@types/node@24.10.4)(sass@1.96.0)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2))': dependencies: '@babel/core': 7.28.5 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5) - '@rolldown/pluginutils': 1.0.0-beta.47 + '@rolldown/pluginutils': 1.0.0-beta.53 '@types/babel__core': 7.20.5 react-refresh: 0.18.0 - vite: 7.1.12(@types/node@24.10.1)(sass@1.94.2)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2) + vite: 7.1.12(@types/node@24.10.4)(sass@1.96.0)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2) transitivePeerDependencies: - supports-color @@ -14647,7 +14583,7 @@ snapshots: obug: 2.1.1 std-env: 3.10.0 tinyrainbow: 3.0.3 - vitest: 4.0.15(@types/node@24.10.1)(@vitest/ui@4.0.15)(jsdom@27.2.0(postcss@8.5.6))(sass@1.94.2)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2) + vitest: 4.0.15(@types/node@24.10.4)(@vitest/ui@4.0.15)(jsdom@27.3.0(postcss@8.5.6))(sass@1.96.0)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2) transitivePeerDependencies: - supports-color @@ -14660,13 +14596,13 @@ snapshots: chai: 6.2.1 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.15(vite@7.1.12(@types/node@24.10.1)(sass@1.94.2)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2))': + '@vitest/mocker@4.0.15(vite@7.1.12(@types/node@24.10.4)(sass@1.96.0)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2))': dependencies: '@vitest/spy': 4.0.15 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.1.12(@types/node@24.10.1)(sass@1.94.2)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2) + vite: 7.1.12(@types/node@24.10.4)(sass@1.96.0)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2) '@vitest/pretty-format@4.0.15': dependencies: @@ -14694,7 +14630,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vitest: 4.0.15(@types/node@24.10.1)(@vitest/ui@4.0.15)(jsdom@27.2.0(postcss@8.5.6))(sass@1.94.2)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2) + vitest: 4.0.15(@types/node@24.10.4)(@vitest/ui@4.0.15)(jsdom@27.3.0(postcss@8.5.6))(sass@1.96.0)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2) '@vitest/utils@4.0.15': dependencies: @@ -15690,7 +15626,7 @@ snapshots: dependencies: env-paths: 2.2.1 import-fresh: 3.3.0 - js-yaml: 4.1.0 + js-yaml: 4.1.1 parse-json: 5.2.0 optionalDependencies: typescript: 5.9.3 @@ -15746,9 +15682,9 @@ snapshots: cssesc@3.0.0: {} - cssstyle@5.3.3(postcss@8.5.6): + cssstyle@5.3.4(postcss@8.5.6): dependencies: - '@asamuzakjp/css-color': 4.0.4 + '@asamuzakjp/css-color': 4.1.0 '@csstools/css-syntax-patches-for-csstree': 1.0.14(postcss@8.5.6) css-tree: 3.1.0 transitivePeerDependencies: @@ -15939,8 +15875,7 @@ snapshots: detect-indent@7.0.1: {} - detect-libc@1.0.3: - optional: true + detect-libc@1.0.3: {} detect-libc@2.1.2: {} @@ -15960,9 +15895,9 @@ snapshots: dependencies: path-type: 4.0.0 - dns-caching@0.2.7: + dns-caching@0.2.9: dependencies: - lru-cache: 11.1.0 + lru-cache: 11.2.4 docker-compose@1.3.0: dependencies: @@ -16000,7 +15935,7 @@ snapshots: dom-walk@0.1.2: {} - dompurify@3.3.0: + dompurify@3.3.1: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -16038,19 +15973,19 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.45.0(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.15.6)(better-sqlite3@12.5.0)(gel@2.0.0)(mysql2@3.15.3)(pg@8.16.3): + drizzle-orm@0.45.1(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(better-sqlite3@12.5.0)(gel@2.0.0)(mysql2@3.15.3)(pg@8.16.3): optionalDependencies: '@libsql/client-wasm': 0.14.0 '@types/better-sqlite3': 7.6.13 - '@types/pg': 8.15.6 + '@types/pg': 8.16.0 better-sqlite3: 12.5.0 gel: 2.0.0 mysql2: 3.15.3 pg: 8.16.3 - drizzle-zod@0.8.3(drizzle-orm@0.45.0(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.15.6)(better-sqlite3@12.5.0)(gel@2.0.0)(mysql2@3.15.3)(pg@8.16.3))(zod@4.1.13): + drizzle-zod@0.8.3(drizzle-orm@0.45.1(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(better-sqlite3@12.5.0)(gel@2.0.0)(mysql2@3.15.3)(pg@8.16.3))(zod@4.1.13): dependencies: - drizzle-orm: 0.45.0(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.15.6)(better-sqlite3@12.5.0)(gel@2.0.0)(mysql2@3.15.3)(pg@8.16.3) + drizzle-orm: 0.45.1(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(better-sqlite3@12.5.0)(gel@2.0.0)(mysql2@3.15.3)(pg@8.16.3) zod: 4.1.13 dunder-proto@1.0.1: @@ -16110,7 +16045,7 @@ snapshots: dependencies: '@types/cookie': 0.4.1 '@types/cors': 2.8.17 - '@types/node': 24.10.1 + '@types/node': 24.10.4 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.7.2 @@ -16459,14 +16394,14 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-config-prettier@10.1.8(eslint@9.39.1): + eslint-config-prettier@10.1.8(eslint@9.39.2): dependencies: - eslint: 9.39.1 + eslint: 9.39.2 - eslint-config-turbo@2.6.3(eslint@9.39.1)(turbo@2.6.3): + eslint-config-turbo@2.6.3(eslint@9.39.2)(turbo@2.6.3): dependencies: - eslint: 9.39.1 - eslint-plugin-turbo: 2.6.3(eslint@9.39.1)(turbo@2.6.3) + eslint: 9.39.2 + eslint-plugin-turbo: 2.6.3(eslint@9.39.2)(turbo@2.6.3) turbo: 2.6.3 eslint-import-resolver-node@0.3.9: @@ -16477,17 +16412,17 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.2)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.2): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.48.1(eslint@9.39.1)(typescript@5.9.3) - eslint: 9.39.1 + '@typescript-eslint/parser': 8.49.0(eslint@9.39.2)(typescript@5.9.3) + eslint: 9.39.2 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -16496,9 +16431,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 9.39.1 + eslint: 9.39.2 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.2)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.2) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -16510,13 +16445,13 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.48.1(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.49.0(eslint@9.39.2)(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.1): + eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.2): dependencies: aria-query: 5.3.2 array-includes: 3.1.8 @@ -16526,7 +16461,7 @@ snapshots: axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - eslint: 9.39.1 + eslint: 9.39.2 hasown: 2.0.2 jsx-ast-utils: 3.3.5 language-tags: 1.0.9 @@ -16535,17 +16470,17 @@ snapshots: safe-regex-test: 1.0.3 string.prototype.includes: 2.0.1 - eslint-plugin-react-hooks@6.1.1(eslint@9.39.1): + eslint-plugin-react-hooks@6.1.1(eslint@9.39.2): dependencies: '@babel/core': 7.28.4 '@babel/parser': 7.28.4 - eslint: 9.39.1 + eslint: 9.39.2 zod: 4.1.13 zod-validation-error: 4.0.2(zod@4.1.13) transitivePeerDependencies: - supports-color - eslint-plugin-react@7.37.5(eslint@9.39.1): + eslint-plugin-react@7.37.5(eslint@9.39.2): dependencies: array-includes: 3.1.8 array.prototype.findlast: 1.2.5 @@ -16553,7 +16488,7 @@ snapshots: array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 es-iterator-helpers: 1.2.1 - eslint: 9.39.1 + eslint: 9.39.2 estraverse: 5.3.0 hasown: 2.0.2 jsx-ast-utils: 3.3.5 @@ -16567,10 +16502,10 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 - eslint-plugin-turbo@2.6.3(eslint@9.39.1)(turbo@2.6.3): + eslint-plugin-turbo@2.6.3(eslint@9.39.2)(turbo@2.6.3): dependencies: dotenv: 16.0.3 - eslint: 9.39.1 + eslint: 9.39.2 turbo: 2.6.3 eslint-scope@5.1.1: @@ -16587,15 +16522,15 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.39.1: + eslint@9.39.2: dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2) '@eslint-community/regexpp': 4.12.1 '@eslint/config-array': 0.21.1 '@eslint/config-helpers': 0.4.2 '@eslint/core': 0.17.0 '@eslint/eslintrc': 3.3.1 - '@eslint/js': 9.39.1 + '@eslint/js': 9.39.2 '@eslint/plugin-kit': 0.4.1 '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 @@ -16729,6 +16664,8 @@ snapshots: fast-equals@5.2.2: {} + fast-equals@5.3.3: {} + fast-fifo@1.3.2: {} fast-glob@3.3.1: @@ -16766,7 +16703,7 @@ snapshots: fast-uri@3.0.6: {} - fast-xml-parser@5.3.2: + fast-xml-parser@5.3.3: dependencies: strnum: 2.1.0 @@ -17153,8 +17090,6 @@ snapshots: chalk: 4.1.2 tinygradient: 1.1.5 - graphemer@1.4.0: {} - h3@1.15.1: dependencies: cookie-es: 1.2.2 @@ -17375,7 +17310,7 @@ snapshots: highlightjs-curl@1.3.0: {} - hono@4.10.7: {} + hono@4.11.1: {} hook-std@4.0.0: {} @@ -17387,7 +17322,7 @@ snapshots: hosted-git-info@9.0.2: dependencies: - lru-cache: 11.2.2 + lru-cache: 11.2.4 hpagent@1.2.0: {} @@ -17830,10 +17765,10 @@ snapshots: isexe@3.1.1: optional: true - isomorphic-dompurify@2.33.0(postcss@8.5.6): + isomorphic-dompurify@2.34.0(postcss@8.5.6): dependencies: - dompurify: 3.3.0 - jsdom: 27.2.0(postcss@8.5.6) + dompurify: 3.3.1 + jsdom: 27.3.0(postcss@8.5.6) transitivePeerDependencies: - bufferutil - canvas @@ -17900,25 +17835,25 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.4 merge-stream: 2.0.0 supports-color: 8.1.1 jose@6.0.8: {} - jotai@2.15.2(@babel/core@7.26.0)(@babel/template@7.27.2)(@types/react@19.2.7)(react@19.2.1): + jotai@2.16.0(@babel/core@7.26.0)(@babel/template@7.27.2)(@types/react@19.2.7)(react@19.2.3): optionalDependencies: '@babel/core': 7.26.0 '@babel/template': 7.27.2 '@types/react': 19.2.7 - react: 19.2.1 + react: 19.2.3 - jotai@2.15.2(@babel/core@7.28.5)(@babel/template@7.27.2)(@types/react@19.2.7)(react@19.2.1): + jotai@2.16.0(@babel/core@7.28.5)(@babel/template@7.27.2)(@types/react@19.2.7)(react@19.2.3): optionalDependencies: '@babel/core': 7.28.5 '@babel/template': 7.27.2 '@types/react': 19.2.7 - react: 19.2.1 + react: 19.2.3 js-base64@3.7.8: {} @@ -17936,11 +17871,11 @@ snapshots: jsbn@1.1.0: {} - jsdom@27.2.0(postcss@8.5.6): + jsdom@27.3.0(postcss@8.5.6): dependencies: - '@acemir/cssom': 0.9.23 - '@asamuzakjp/dom-selector': 6.7.4 - cssstyle: 5.3.3(postcss@8.5.6) + '@acemir/cssom': 0.9.28 + '@asamuzakjp/dom-selector': 6.7.6 + cssstyle: 5.3.4(postcss@8.5.6) data-urls: 6.0.0 decimal.js: 10.6.0 html-encoding-sniffer: 4.0.0 @@ -18041,13 +17976,12 @@ snapshots: dependencies: readable-stream: 2.3.8 - ldapts@8.0.12: + ldapts@8.0.23: dependencies: '@types/asn1': 0.2.4 asn1: 0.2.6 debug: 4.4.3 strict-event-emitter-types: 2.0.0 - uuid: 13.0.0 whatwg-url: 15.1.0 transitivePeerDependencies: - supports-color @@ -18163,10 +18097,10 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@11.1.0: {} - lru-cache@11.2.2: {} + lru-cache@11.2.4: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -18201,24 +18135,24 @@ snapshots: make-error@1.3.6: {} - mantine-form-zod-resolver@1.3.0(@mantine/form@8.3.9(react@19.2.1))(zod@4.1.13): + mantine-form-zod-resolver@1.3.0(@mantine/form@8.3.10(react@19.2.3))(zod@4.1.13): dependencies: - '@mantine/form': 8.3.9(react@19.2.1) + '@mantine/form': 8.3.10(react@19.2.3) zod: 4.1.13 - mantine-react-table@2.0.0-beta.9(@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/dates@8.3.9(@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/hooks@8.3.9(react@19.2.1))(dayjs@1.11.19)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/hooks@8.3.9(react@19.2.1))(@tabler/icons-react@3.35.0(react@19.2.1))(clsx@2.1.1)(dayjs@1.11.19)(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + mantine-react-table@2.0.0-beta.9(@mantine/core@8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/dates@8.3.10(@mantine/core@8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/hooks@8.3.10(react@19.2.3))(dayjs@1.11.19)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/hooks@8.3.10(react@19.2.3))(@tabler/icons-react@3.35.0(react@19.2.3))(clsx@2.1.1)(dayjs@1.11.19)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - '@mantine/core': 8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@mantine/dates': 8.3.9(@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@mantine/hooks@8.3.9(react@19.2.1))(dayjs@1.11.19)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@mantine/hooks': 8.3.9(react@19.2.1) - '@tabler/icons-react': 3.35.0(react@19.2.1) + '@mantine/core': 8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@mantine/dates': 8.3.10(@mantine/core@8.3.10(@mantine/hooks@8.3.10(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mantine/hooks@8.3.10(react@19.2.3))(dayjs@1.11.19)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@mantine/hooks': 8.3.10(react@19.2.3) + '@tabler/icons-react': 3.35.0(react@19.2.3) '@tanstack/match-sorter-utils': 8.19.4 - '@tanstack/react-table': 8.20.5(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@tanstack/react-virtual': 3.11.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@tanstack/react-table': 8.20.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@tanstack/react-virtual': 3.11.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 dayjs: 1.11.19 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) maria2@0.4.1: {} @@ -18724,74 +18658,75 @@ snapshots: netmask@2.0.2: {} - next-auth@5.0.0-beta.30(next@16.0.7(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2))(react@19.2.1): + next-auth@5.0.0-beta.30(next@16.0.10(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.96.0))(react@19.2.3): dependencies: '@auth/core': 0.41.0 - next: 16.0.7(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2) - react: 19.2.1 + next: 16.0.10(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.96.0) + react: 19.2.3 - next-intl-swc-plugin-extractor@4.5.8: {} + next-intl-swc-plugin-extractor@4.6.0: {} - next-intl@4.5.8(next@16.0.7(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2))(react@19.2.1)(typescript@5.9.3): + next-intl@4.6.0(next@16.0.10(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.96.0))(react@19.2.3)(typescript@5.9.3): dependencies: '@formatjs/intl-localematcher': 0.5.5 + '@parcel/watcher': 2.4.1 '@swc/core': 1.15.3 negotiator: 1.0.0 - next: 16.0.7(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2) - next-intl-swc-plugin-extractor: 4.5.8 - po-parser: 1.0.2 - react: 19.2.1 - use-intl: 4.5.8(react@19.2.1) + next: 16.0.10(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.96.0) + next-intl-swc-plugin-extractor: 4.6.0 + po-parser: 2.0.0 + react: 19.2.3 + use-intl: 4.6.0(react@19.2.3) optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: - '@swc/helpers' - next@16.0.7(@babel/core@7.26.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2): + next@16.0.10(@babel/core@7.26.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.96.0): dependencies: - '@next/env': 16.0.7 + '@next/env': 16.0.10 '@swc/helpers': 0.5.15 caniuse-lite: 1.0.30001755 postcss: 8.4.31 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - styled-jsx: 5.1.6(@babel/core@7.26.0)(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + styled-jsx: 5.1.6(@babel/core@7.26.0)(react@19.2.3) optionalDependencies: - '@next/swc-darwin-arm64': 16.0.7 - '@next/swc-darwin-x64': 16.0.7 - '@next/swc-linux-arm64-gnu': 16.0.7 - '@next/swc-linux-arm64-musl': 16.0.7 - '@next/swc-linux-x64-gnu': 16.0.7 - '@next/swc-linux-x64-musl': 16.0.7 - '@next/swc-win32-arm64-msvc': 16.0.7 - '@next/swc-win32-x64-msvc': 16.0.7 + '@next/swc-darwin-arm64': 16.0.10 + '@next/swc-darwin-x64': 16.0.10 + '@next/swc-linux-arm64-gnu': 16.0.10 + '@next/swc-linux-arm64-musl': 16.0.10 + '@next/swc-linux-x64-gnu': 16.0.10 + '@next/swc-linux-x64-musl': 16.0.10 + '@next/swc-win32-arm64-msvc': 16.0.10 + '@next/swc-win32-x64-msvc': 16.0.10 babel-plugin-react-compiler: 1.0.0 - sass: 1.94.2 + sass: 1.96.0 sharp: 0.34.4 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros - next@16.0.7(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2): + next@16.0.10(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.96.0): dependencies: - '@next/env': 16.0.7 + '@next/env': 16.0.10 '@swc/helpers': 0.5.15 caniuse-lite: 1.0.30001755 postcss: 8.4.31 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - styled-jsx: 5.1.6(@babel/core@7.28.5)(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + styled-jsx: 5.1.6(@babel/core@7.28.5)(react@19.2.3) optionalDependencies: - '@next/swc-darwin-arm64': 16.0.7 - '@next/swc-darwin-x64': 16.0.7 - '@next/swc-linux-arm64-gnu': 16.0.7 - '@next/swc-linux-arm64-musl': 16.0.7 - '@next/swc-linux-x64-gnu': 16.0.7 - '@next/swc-linux-x64-musl': 16.0.7 - '@next/swc-win32-arm64-msvc': 16.0.7 - '@next/swc-win32-x64-msvc': 16.0.7 + '@next/swc-darwin-arm64': 16.0.10 + '@next/swc-darwin-x64': 16.0.10 + '@next/swc-linux-arm64-gnu': 16.0.10 + '@next/swc-linux-arm64-musl': 16.0.10 + '@next/swc-linux-x64-gnu': 16.0.10 + '@next/swc-linux-x64-musl': 16.0.10 + '@next/swc-win32-arm64-msvc': 16.0.10 + '@next/swc-win32-x64-msvc': 16.0.10 babel-plugin-react-compiler: 1.0.0 - sass: 1.94.2 + sass: 1.96.0 sharp: 0.34.4 transitivePeerDependencies: - '@babel/core' @@ -18814,8 +18749,7 @@ snapshots: node-addon-api@3.2.1: optional: true - node-addon-api@7.1.1: - optional: true + node-addon-api@7.1.1: {} node-addon-api@8.3.0: {} @@ -19303,7 +19237,7 @@ snapshots: optionalDependencies: fsevents: 2.3.2 - po-parser@1.0.2: {} + po-parser@2.0.0: {} possible-typed-array-names@1.0.0: {} @@ -19420,7 +19354,7 @@ snapshots: dependencies: loose-envify: 1.4.0 object-assign: 4.1.1 - react-is: 19.2.1 + react-is: 19.2.3 proper-lockfile@4.1.2: dependencies: @@ -19452,20 +19386,20 @@ snapshots: dependencies: prosemirror-state: 1.4.3 prosemirror-transform: 1.10.2 - prosemirror-view: 1.37.2 + prosemirror-view: 1.41.4 prosemirror-gapcursor@1.3.2: dependencies: prosemirror-keymap: 1.2.2 prosemirror-model: 1.24.1 prosemirror-state: 1.4.3 - prosemirror-view: 1.37.2 + prosemirror-view: 1.41.4 prosemirror-history@1.4.1: dependencies: prosemirror-state: 1.4.3 prosemirror-transform: 1.10.2 - prosemirror-view: 1.37.2 + prosemirror-view: 1.41.4 rope-sequence: 1.3.4 prosemirror-inputrules@1.4.0: @@ -19499,7 +19433,7 @@ snapshots: dependencies: prosemirror-model: 1.24.1 - prosemirror-schema-list@1.4.1: + prosemirror-schema-list@1.5.1: dependencies: prosemirror-model: 1.24.1 prosemirror-state: 1.4.3 @@ -19509,7 +19443,7 @@ snapshots: dependencies: prosemirror-model: 1.24.1 prosemirror-transform: 1.10.2 - prosemirror-view: 1.37.2 + prosemirror-view: 1.41.4 prosemirror-tables@1.6.4: dependencies: @@ -19517,21 +19451,21 @@ snapshots: prosemirror-model: 1.24.1 prosemirror-state: 1.4.3 prosemirror-transform: 1.10.2 - prosemirror-view: 1.37.2 + prosemirror-view: 1.41.4 - prosemirror-trailing-node@3.0.0(prosemirror-model@1.24.1)(prosemirror-state@1.4.3)(prosemirror-view@1.37.2): + prosemirror-trailing-node@3.0.0(prosemirror-model@1.24.1)(prosemirror-state@1.4.3)(prosemirror-view@1.41.4): dependencies: '@remirror/core-constants': 3.0.0 escape-string-regexp: 4.0.0 prosemirror-model: 1.24.1 prosemirror-state: 1.4.3 - prosemirror-view: 1.37.2 + prosemirror-view: 1.41.4 prosemirror-transform@1.10.2: dependencies: prosemirror-model: 1.24.1 - prosemirror-view@1.37.2: + prosemirror-view@1.41.4: dependencies: prosemirror-model: 1.24.1 prosemirror-state: 1.4.3 @@ -19551,7 +19485,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 24.10.1 + '@types/node': 24.10.4 long: 5.2.3 proxmox-api@1.1.1: @@ -19635,26 +19569,26 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 - react-dom@19.2.1(react@19.2.1): + react-dom@19.2.3(react@19.2.3): dependencies: - react: 19.2.1 + react: 19.2.3 scheduler: 0.27.0 - react-dropzone@14.3.8(react@19.2.1): + react-dropzone@14.3.8(react@19.2.3): dependencies: attr-accept: 2.2.5 file-selector: 2.1.2 prop-types: 15.8.1 - react: 19.2.1 + react: 19.2.3 - react-error-boundary@6.0.0(react@19.2.1): + react-error-boundary@6.0.0(react@19.2.3): dependencies: '@babel/runtime': 7.28.4 - react: 19.2.1 + react: 19.2.3 - react-is@19.2.1: {} + react-is@19.2.3: {} - react-markdown@10.1.0(@types/react@19.2.7)(react@19.2.1): + react-markdown@10.1.0(@types/react@19.2.7)(react@19.2.3): dependencies: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 @@ -19663,7 +19597,7 @@ snapshots: hast-util-to-jsx-runtime: 2.3.6 html-url-attributes: 3.0.1 mdast-util-to-hast: 13.2.0 - react: 19.2.1 + react: 19.2.3 remark-parse: 11.0.0 remark-rehype: 11.1.1 unified: 11.0.5 @@ -19672,29 +19606,29 @@ snapshots: transitivePeerDependencies: - supports-color - react-number-format@5.4.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + react-number-format@5.4.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) react-refresh@0.18.0: {} - react-remove-scroll-bar@2.3.8(@types/react@19.2.7)(react@19.2.1): + react-remove-scroll-bar@2.3.8(@types/react@19.2.7)(react@19.2.3): dependencies: - react: 19.2.1 - react-style-singleton: 2.2.3(@types/react@19.2.7)(react@19.2.1) + react: 19.2.3 + react-style-singleton: 2.2.3(@types/react@19.2.7)(react@19.2.3) tslib: 2.8.1 optionalDependencies: '@types/react': 19.2.7 - react-remove-scroll@2.7.1(@types/react@19.2.7)(react@19.2.1): + react-remove-scroll@2.7.1(@types/react@19.2.7)(react@19.2.3): dependencies: - react: 19.2.1 - react-remove-scroll-bar: 2.3.8(@types/react@19.2.7)(react@19.2.1) - react-style-singleton: 2.2.3(@types/react@19.2.7)(react@19.2.1) + react: 19.2.3 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.7)(react@19.2.3) + react-style-singleton: 2.2.3(@types/react@19.2.7)(react@19.2.3) tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@19.2.7)(react@19.2.1) - use-sidecar: 1.1.3(@types/react@19.2.7)(react@19.2.1) + use-callback-ref: 1.3.3(@types/react@19.2.7)(react@19.2.3) + use-sidecar: 1.1.3(@types/react@19.2.7)(react@19.2.3) optionalDependencies: '@types/react': 19.2.7 @@ -19706,46 +19640,46 @@ snapshots: mri: 1.2.0 playwright: 1.49.0 - react-simple-code-editor@0.14.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + react-simple-code-editor@0.14.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - react-smooth@4.0.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + react-smooth@4.0.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: fast-equals: 5.2.2 prop-types: 15.8.1 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - react-transition-group: 4.4.5(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + react-transition-group: 4.4.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - react-style-singleton@2.2.3(@types/react@19.2.7)(react@19.2.1): + react-style-singleton@2.2.3(@types/react@19.2.7)(react@19.2.3): dependencies: get-nonce: 1.0.1 - react: 19.2.1 + react: 19.2.3 tslib: 2.8.1 optionalDependencies: '@types/react': 19.2.7 - react-textarea-autosize@8.5.9(@types/react@19.2.7)(react@19.2.1): + react-textarea-autosize@8.5.9(@types/react@19.2.7)(react@19.2.3): dependencies: '@babel/runtime': 7.28.4 - react: 19.2.1 - use-composed-ref: 1.3.0(react@19.2.1) - use-latest: 1.2.1(@types/react@19.2.7)(react@19.2.1) + react: 19.2.3 + use-composed-ref: 1.3.0(react@19.2.3) + use-latest: 1.2.1(@types/react@19.2.7)(react@19.2.3) transitivePeerDependencies: - '@types/react' - react-transition-group@4.4.5(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + react-transition-group@4.4.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: '@babel/runtime': 7.28.4 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - react@19.2.1: {} + react@19.2.3: {} read-package-up@11.0.0: dependencies: @@ -19819,15 +19753,15 @@ snapshots: dependencies: decimal.js-light: 2.5.1 - recharts@2.15.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + recharts@2.15.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: clsx: 2.1.1 eventemitter3: 4.0.7 lodash: 4.17.21 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - react-is: 19.2.1 - react-smooth: 4.0.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + react-is: 19.2.3 + react-smooth: 4.0.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3) recharts-scale: 0.4.5 tiny-invariant: 1.3.3 victory-vendor: 36.9.2 @@ -20121,7 +20055,7 @@ snapshots: safer-buffer@2.1.2: {} - sass@1.94.2: + sass@1.96.0: dependencies: chokidar: 4.0.0 immutable: 5.0.2 @@ -20157,7 +20091,7 @@ snapshots: '@semantic-release/commit-analyzer': 13.0.1(semantic-release@25.0.2(typescript@5.9.3)) '@semantic-release/error': 4.0.0 '@semantic-release/github': 12.0.2(semantic-release@25.0.2(typescript@5.9.3)) - '@semantic-release/npm': 13.1.2(semantic-release@25.0.2(typescript@5.9.3)) + '@semantic-release/npm': 13.1.3(semantic-release@25.0.2(typescript@5.9.3)) '@semantic-release/release-notes-generator': 14.1.0(semantic-release@25.0.2(typescript@5.9.3)) aggregate-error: 5.0.0 cosmiconfig: 9.0.0(typescript@5.9.3) @@ -20633,17 +20567,17 @@ snapshots: dependencies: inline-style-parser: 0.2.4 - styled-jsx@5.1.6(@babel/core@7.26.0)(react@19.2.1): + styled-jsx@5.1.6(@babel/core@7.26.0)(react@19.2.3): dependencies: client-only: 0.0.1 - react: 19.2.1 + react: 19.2.3 optionalDependencies: '@babel/core': 7.26.0 - styled-jsx@5.1.6(@babel/core@7.28.5)(react@19.2.1): + styled-jsx@5.1.6(@babel/core@7.28.5)(react@19.2.3): dependencies: client-only: 0.0.1 - react: 19.2.1 + react: 19.2.3 optionalDependencies: '@babel/core': 7.28.5 @@ -20767,7 +20701,7 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 - testcontainers@11.9.0: + testcontainers@11.10.0: dependencies: '@balena/dockerignore': 1.0.2 '@types/dockerode': 3.3.47 @@ -20842,10 +20776,6 @@ snapshots: tinyrainbow@3.0.3: {} - tippy.js@6.3.7: - dependencies: - '@popperjs/core': 2.11.8 - title-case@2.1.1: dependencies: no-case: 2.3.2 @@ -20899,9 +20829,9 @@ snapshots: trough@2.2.0: {} - trpc-to-openapi@3.1.0(patch_hash=2ca3c16af0fcca0c736697ad4fe553a14f794524fa9ce0d5c3e8ee4aea76090c)(@trpc/server@11.7.2(typescript@5.9.3))(zod-openapi@5.3.0(zod@4.1.13))(zod@4.1.13): + trpc-to-openapi@3.1.0(patch_hash=2ca3c16af0fcca0c736697ad4fe553a14f794524fa9ce0d5c3e8ee4aea76090c)(@trpc/server@11.8.0(typescript@5.9.3))(zod-openapi@5.3.0(zod@4.1.13))(zod@4.1.13): dependencies: - '@trpc/server': 11.7.2(typescript@5.9.3) + '@trpc/server': 11.8.0(typescript@5.9.3) co-body: 6.2.0 h3: 1.15.1 openapi3-ts: 4.4.0 @@ -20916,14 +20846,14 @@ snapshots: ts-deepmerge@7.0.3: {} - ts-node@10.9.2(@swc/core@1.15.3)(@types/node@24.10.1)(typescript@5.9.3): + ts-node@10.9.2(@swc/core@1.15.3)(@types/node@24.10.4)(typescript@5.9.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 24.10.1 + '@types/node': 24.10.4 acorn: 8.15.0 acorn-walk: 8.3.4 arg: 4.1.3 @@ -21113,13 +21043,13 @@ snapshots: dependencies: is-typedarray: 1.0.0 - typescript-eslint@8.48.1(eslint@9.39.1)(typescript@5.9.3): + typescript-eslint@8.49.0(eslint@9.39.2)(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/parser': 8.48.1(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) - eslint: 9.39.1 + '@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/parser': 8.49.0(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.2)(typescript@5.9.3) + eslint: 9.39.2 typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -21295,61 +21225,59 @@ snapshots: url-toolkit@2.2.5: {} - use-callback-ref@1.3.3(@types/react@19.2.7)(react@19.2.1): + use-callback-ref@1.3.3(@types/react@19.2.7)(react@19.2.3): dependencies: - react: 19.2.1 + react: 19.2.3 tslib: 2.8.1 optionalDependencies: '@types/react': 19.2.7 - use-composed-ref@1.3.0(react@19.2.1): + use-composed-ref@1.3.0(react@19.2.3): dependencies: - react: 19.2.1 + react: 19.2.3 - use-deep-compare-effect@1.8.1(react@19.2.1): + use-deep-compare-effect@1.8.1(react@19.2.3): dependencies: '@babel/runtime': 7.28.4 dequal: 2.0.3 - react: 19.2.1 + react: 19.2.3 - use-intl@4.5.8(react@19.2.1): + use-intl@4.6.0(react@19.2.3): dependencies: '@formatjs/fast-memoize': 2.2.1 '@schummar/icu-type-parser': 1.21.5 intl-messageformat: 10.7.1 - react: 19.2.1 + react: 19.2.3 - use-isomorphic-layout-effect@1.1.2(@types/react@19.2.7)(react@19.2.1): + use-isomorphic-layout-effect@1.1.2(@types/react@19.2.7)(react@19.2.3): dependencies: - react: 19.2.1 + react: 19.2.3 optionalDependencies: '@types/react': 19.2.7 - use-latest@1.2.1(@types/react@19.2.7)(react@19.2.1): + use-latest@1.2.1(@types/react@19.2.7)(react@19.2.3): dependencies: - react: 19.2.1 - use-isomorphic-layout-effect: 1.1.2(@types/react@19.2.7)(react@19.2.1) + react: 19.2.3 + use-isomorphic-layout-effect: 1.1.2(@types/react@19.2.7)(react@19.2.3) optionalDependencies: '@types/react': 19.2.7 - use-sidecar@1.1.3(@types/react@19.2.7)(react@19.2.1): + use-sidecar@1.1.3(@types/react@19.2.7)(react@19.2.3): dependencies: detect-node-es: 1.1.0 - react: 19.2.1 + react: 19.2.3 tslib: 2.8.1 optionalDependencies: '@types/react': 19.2.7 - use-sync-external-store@1.4.0(react@19.2.1): + use-sync-external-store@1.4.0(react@19.2.3): dependencies: - react: 19.2.1 + react: 19.2.3 util-deprecate@1.0.2: {} uuid@10.0.0: {} - uuid@13.0.0: {} - uuid@8.3.2: {} v8-compile-cache-lib@3.0.1: {} @@ -21421,18 +21349,18 @@ snapshots: dependencies: global: 4.4.0 - vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.1.12(@types/node@24.10.1)(sass@1.94.2)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2)): + vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.1.12(@types/node@24.10.4)(sass@1.96.0)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2)): dependencies: debug: 4.3.7 globrex: 0.1.2 tsconfck: 3.1.3(typescript@5.9.3) optionalDependencies: - vite: 7.1.12(@types/node@24.10.1)(sass@1.94.2)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2) + vite: 7.1.12(@types/node@24.10.4)(sass@1.96.0)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2) transitivePeerDependencies: - supports-color - typescript - vite@7.1.12(@types/node@24.10.1)(sass@1.94.2)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2): + vite@7.1.12(@types/node@24.10.4)(sass@1.96.0)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -21441,18 +21369,18 @@ snapshots: rollup: 4.48.1 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.4 fsevents: 2.3.3 - sass: 1.94.2 + sass: 1.96.0 sugarss: 5.0.0(postcss@8.5.6) terser: 5.44.1 tsx: 4.20.5 yaml: 2.8.2 - vitest@4.0.15(@types/node@24.10.1)(@vitest/ui@4.0.15)(jsdom@27.2.0(postcss@8.5.6))(sass@1.94.2)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2): + vitest@4.0.15(@types/node@24.10.4)(@vitest/ui@4.0.15)(jsdom@27.3.0(postcss@8.5.6))(sass@1.96.0)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2): dependencies: '@vitest/expect': 4.0.15 - '@vitest/mocker': 4.0.15(vite@7.1.12(@types/node@24.10.1)(sass@1.94.2)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2)) + '@vitest/mocker': 4.0.15(vite@7.1.12(@types/node@24.10.4)(sass@1.96.0)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2)) '@vitest/pretty-format': 4.0.15 '@vitest/runner': 4.0.15 '@vitest/snapshot': 4.0.15 @@ -21469,12 +21397,12 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.1.12(@types/node@24.10.1)(sass@1.94.2)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2) + vite: 7.1.12(@types/node@24.10.4)(sass@1.96.0)(sugarss@5.0.0(postcss@8.5.6))(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.4 '@vitest/ui': 4.0.15(vitest@4.0.15) - jsdom: 27.2.0(postcss@8.5.6) + jsdom: 27.3.0(postcss@8.5.6) transitivePeerDependencies: - jiti - less @@ -21674,7 +21602,7 @@ snapshots: readable-stream: 3.6.2 triple-beam: 1.4.1 - winston@3.18.3: + winston@3.19.0: dependencies: '@colors/colors': 1.6.0 '@dabh/diagnostics': 2.0.8 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 25da02bdf..8a392051e 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,4 +5,4 @@ packages: minimumReleaseAge: 4320 # Only download deps if they are at least 3 days old (in minutes) minimumReleaseAgeExclude: - next - - "@next/eslint-plugin-next" + - "@next/*" diff --git a/scripts/run.sh b/scripts/run.sh index f1220d9eb..78659a04a 100644 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -10,7 +10,8 @@ if [ "$DB_MIGRATIONS_DISABLED" = "true" ]; then echo "DB migrations are disabled, skipping" else echo "Running DB migrations" - node ./db/migrations/$DB_DIALECT/migrate.cjs ./db/migrations/$DB_DIALECT + # We disable redis logs during migration as the redis client is not yet started + DISABLE_REDIS_LOGS=true node ./db/migrations/$DB_DIALECT/migrate.cjs ./db/migrations/$DB_DIALECT fi # Auth secret is generated every time the container starts as it is required, but not used because we don't need JWTs or Mail hashing diff --git a/static-data/contributors.json b/static-data/contributors.json index 8ee630681..fa266ad25 100644 --- a/static-data/contributors.json +++ b/static-data/contributors.json @@ -1 +1 @@ -[{"login":"ajnart","avatar_url":"https://avatars.githubusercontent.com/u/49837342?v=4"},{"login":"manuel-rw","avatar_url":"https://avatars.githubusercontent.com/u/30572287?v=4"},{"login":"Meierschlumpf","avatar_url":"https://avatars.githubusercontent.com/u/63781622?v=4"},{"login":"SeDemal","avatar_url":"https://avatars.githubusercontent.com/u/26098587?v=4"},{"login":"lammersbjorn","avatar_url":"https://avatars.githubusercontent.com/u/71191962?v=4"},{"login":"LarveyOfficial","avatar_url":"https://avatars.githubusercontent.com/u/39219859?v=4"},{"login":"c00ldude1oo","avatar_url":"https://avatars.githubusercontent.com/u/15677803?v=4"},{"login":"InterN0te","avatar_url":"https://avatars.githubusercontent.com/u/10882916?v=4"},{"login":"Aimsucks","avatar_url":"https://avatars.githubusercontent.com/u/1828486?v=4"},{"login":"JannesV","avatar_url":"https://avatars.githubusercontent.com/u/5984244?v=4"},{"login":"MauriceNino","avatar_url":"https://avatars.githubusercontent.com/u/18658092?v=4"},{"login":"hillaliy","avatar_url":"https://avatars.githubusercontent.com/u/950010?v=4"},{"login":"Skeeww","avatar_url":"https://avatars.githubusercontent.com/u/9639992?v=4"},{"login":"RichyHBM","avatar_url":"https://avatars.githubusercontent.com/u/5472275?v=4"},{"login":"Bonfire","avatar_url":"https://avatars.githubusercontent.com/u/5704760?v=4"},{"login":"wieli99","avatar_url":"https://avatars.githubusercontent.com/u/46794825?v=4"},{"login":"ishaanparlikar","avatar_url":"https://avatars.githubusercontent.com/u/43013132?v=4"},{"login":"Zareix","avatar_url":"https://avatars.githubusercontent.com/u/29818713?v=4"},{"login":"tuggan","avatar_url":"https://avatars.githubusercontent.com/u/4325608?v=4"},{"login":"Spillebulle","avatar_url":"https://avatars.githubusercontent.com/u/46653946?v=4"},{"login":"tancak","avatar_url":"https://avatars.githubusercontent.com/u/11760749?v=4"},{"login":"spkesDE","avatar_url":"https://avatars.githubusercontent.com/u/1682314?v=4"},{"login":"j3lte","avatar_url":"https://avatars.githubusercontent.com/u/2557568?v=4"},{"login":"dslatt","avatar_url":"https://avatars.githubusercontent.com/u/5211001?v=4"},{"login":"Malong11-007","avatar_url":"https://avatars.githubusercontent.com/u/40298510?v=4"},{"login":"StefanB7","avatar_url":"https://avatars.githubusercontent.com/u/22056509?v=4"},{"login":"souravghosh01","avatar_url":"https://avatars.githubusercontent.com/u/37548139?v=4"},{"login":"SR3u","avatar_url":"https://avatars.githubusercontent.com/u/2380522?v=4"},{"login":"Rikpat","avatar_url":"https://avatars.githubusercontent.com/u/33869814?v=4"},{"login":"NormanJS","avatar_url":"https://avatars.githubusercontent.com/u/83409549?v=4"},{"login":"No-Maines-Land","avatar_url":"https://avatars.githubusercontent.com/u/20687725?v=4"},{"login":"stark1tty","avatar_url":"https://avatars.githubusercontent.com/u/69326376?v=4"},{"login":"Momcilo42","avatar_url":"https://avatars.githubusercontent.com/u/10118137?v=4"},{"login":"marius-arch","avatar_url":"https://avatars.githubusercontent.com/u/72494359?v=4"},{"login":"Lumilias","avatar_url":"https://avatars.githubusercontent.com/u/10852161?v=4"},{"login":"Havock94","avatar_url":"https://avatars.githubusercontent.com/u/7635248?v=4"},{"login":"Lollyde","avatar_url":"https://avatars.githubusercontent.com/u/11347524?v=4"},{"login":"krishnamuppaneni","avatar_url":"https://avatars.githubusercontent.com/u/4703366?v=4"},{"login":"devtobi","avatar_url":"https://avatars.githubusercontent.com/u/28538704?v=4"},{"login":"TyxTang","avatar_url":"https://avatars.githubusercontent.com/u/90897955?v=4"},{"login":"alotlikebeans","avatar_url":"https://avatars.githubusercontent.com/u/19630511?v=4"},{"login":"WillyJL","avatar_url":"https://avatars.githubusercontent.com/u/49810075?v=4"},{"login":"anonysoul","avatar_url":"https://avatars.githubusercontent.com/u/105715150?v=4"},{"login":"cospeedster","avatar_url":"https://avatars.githubusercontent.com/u/52749645?v=4"},{"login":"diederbert","avatar_url":"https://avatars.githubusercontent.com/u/162878798?v=4"},{"login":"digdilem","avatar_url":"https://avatars.githubusercontent.com/u/4052275?v=4"},{"login":"dinghino","avatar_url":"https://avatars.githubusercontent.com/u/14729459?v=4"},{"login":"gfomichev","avatar_url":"https://avatars.githubusercontent.com/u/1596146?v=4"},{"login":"gnattu","avatar_url":"https://avatars.githubusercontent.com/u/12995396?v=4"},{"login":"tuxpizza","avatar_url":"https://avatars.githubusercontent.com/u/84710786?v=4"},{"login":"xmarkurion","avatar_url":"https://avatars.githubusercontent.com/u/46654687?v=4"},{"login":"huanga","avatar_url":"https://avatars.githubusercontent.com/u/732970?v=4"},{"login":"taos15","avatar_url":"https://avatars.githubusercontent.com/u/122130728?v=4"},{"login":"arghyadipchak","avatar_url":"https://avatars.githubusercontent.com/u/32430374?v=4"},{"login":"AuthorShin","avatar_url":"https://avatars.githubusercontent.com/u/4959043?v=4"},{"login":"benniblot","avatar_url":"https://avatars.githubusercontent.com/u/47495914?v=4"},{"login":"bebsworthy","avatar_url":"https://avatars.githubusercontent.com/u/5266759?v=4"},{"login":"catrielmuller","avatar_url":"https://avatars.githubusercontent.com/u/2272323?v=4"},{"login":"Vysp3r","avatar_url":"https://avatars.githubusercontent.com/u/62019735?v=4"},{"login":"DR-Blogs","avatar_url":"https://avatars.githubusercontent.com/u/96393186?v=4"},{"login":"singular0","avatar_url":"https://avatars.githubusercontent.com/u/1925883?v=4"},{"login":"diogovalentte","avatar_url":"https://avatars.githubusercontent.com/u/49578155?v=4"},{"login":"codezninja","avatar_url":"https://avatars.githubusercontent.com/u/4400773?v=4"},{"login":"fabricionaweb","avatar_url":"https://avatars.githubusercontent.com/u/15933?v=4"},{"login":"nyok1912","avatar_url":"https://avatars.githubusercontent.com/u/32163949?v=4"},{"login":"bo3bdo","avatar_url":"https://avatars.githubusercontent.com/u/5271380?v=4"},{"login":"hskrtich","avatar_url":"https://avatars.githubusercontent.com/u/1214484?v=4"},{"login":"ishults","avatar_url":"https://avatars.githubusercontent.com/u/7275442?v=4"},{"login":"jelliuk","avatar_url":"https://avatars.githubusercontent.com/u/4790959?v=4"},{"login":"Moohan","avatar_url":"https://avatars.githubusercontent.com/u/5982260?v=4"},{"login":"tesar-tech","avatar_url":"https://avatars.githubusercontent.com/u/33880579?v=4"},{"login":"jbruell","avatar_url":"https://avatars.githubusercontent.com/u/31685172?v=4"},{"login":"JasonLeeB06","avatar_url":"https://avatars.githubusercontent.com/u/82266978?v=4"},{"login":"jeffersonraimon","avatar_url":"https://avatars.githubusercontent.com/u/80064475?v=4"},{"login":"jo-chemla","avatar_url":"https://avatars.githubusercontent.com/u/16822841?v=4"},{"login":"joser93","avatar_url":"https://avatars.githubusercontent.com/u/6912089?v=4"},{"login":"justijndepover","avatar_url":"https://avatars.githubusercontent.com/u/9008623?v=4"},{"login":"Aandree5","avatar_url":"https://avatars.githubusercontent.com/u/32734153?v=4"},{"login":"jackwellerreal","avatar_url":"https://avatars.githubusercontent.com/u/97435840?v=4"},{"login":"toineenzo","avatar_url":"https://avatars.githubusercontent.com/u/4921397?v=4"},{"login":"oben01","avatar_url":"https://avatars.githubusercontent.com/u/22369027?v=4"},{"login":"rezstje","avatar_url":"https://avatars.githubusercontent.com/u/45420315?v=4"},{"login":"MaienM","avatar_url":"https://avatars.githubusercontent.com/u/583129?v=4"},{"login":"xoxyuxu","avatar_url":"https://avatars.githubusercontent.com/u/3543416?v=4"},{"login":"castielwaverly","avatar_url":"https://avatars.githubusercontent.com/u/137005689?v=4"},{"login":"AartSchinkel","avatar_url":"https://avatars.githubusercontent.com/u/189675191?v=4"},{"login":"m0s4ik","avatar_url":"https://avatars.githubusercontent.com/u/26578395?v=4"},{"login":"Oupsman","avatar_url":"https://avatars.githubusercontent.com/u/11260343?v=4"},{"login":"Copilot","avatar_url":"https://avatars.githubusercontent.com/in/1143301?v=4"},{"login":"DZ945brH","avatar_url":"https://avatars.githubusercontent.com/u/42635243?v=4"},{"login":"ieugen","avatar_url":"https://avatars.githubusercontent.com/u/1050920?v=4"},{"login":"faugusto-oliveira","avatar_url":"https://avatars.githubusercontent.com/u/37384074?v=4"},{"login":"frankvHoof93","avatar_url":"https://avatars.githubusercontent.com/u/7346516?v=4"},{"login":"HeapReaper","avatar_url":"https://avatars.githubusercontent.com/u/31496522?v=4"},{"login":"Kaotic3","avatar_url":"https://avatars.githubusercontent.com/u/80522225?v=4"},{"login":"hotrungnhan","avatar_url":"https://avatars.githubusercontent.com/u/40029631?v=4"},{"login":"leandroscardua","avatar_url":"https://avatars.githubusercontent.com/u/24698541?v=4"},{"login":"itzTheMeow","avatar_url":"https://avatars.githubusercontent.com/u/50887230?v=4"},{"login":"NicolasNewman","avatar_url":"https://avatars.githubusercontent.com/u/16135852?v=4"},{"login":"NikeLaosClericus","avatar_url":"https://avatars.githubusercontent.com/u/39810627?v=4"},{"login":"noahcroghan","avatar_url":"https://avatars.githubusercontent.com/u/27422774?v=4"},{"login":"generalmat82","avatar_url":"https://avatars.githubusercontent.com/u/110256073?v=4"},{"login":"lthomsen","avatar_url":"https://avatars.githubusercontent.com/u/28560325?v=4"},{"login":"pitschi","avatar_url":"https://avatars.githubusercontent.com/u/6841565?v=4"},{"login":"tech62","avatar_url":"https://avatars.githubusercontent.com/u/16237642?v=4"}] \ No newline at end of file +[{"login":"ajnart","avatar_url":"https://avatars.githubusercontent.com/u/49837342?v=4"},{"login":"manuel-rw","avatar_url":"https://avatars.githubusercontent.com/u/30572287?v=4"},{"login":"Meierschlumpf","avatar_url":"https://avatars.githubusercontent.com/u/63781622?v=4"},{"login":"SeDemal","avatar_url":"https://avatars.githubusercontent.com/u/26098587?v=4"},{"login":"lammersbjorn","avatar_url":"https://avatars.githubusercontent.com/u/71191962?v=4"},{"login":"LarveyOfficial","avatar_url":"https://avatars.githubusercontent.com/u/39219859?v=4"},{"login":"c00ldude1oo","avatar_url":"https://avatars.githubusercontent.com/u/15677803?v=4"},{"login":"InterN0te","avatar_url":"https://avatars.githubusercontent.com/u/10882916?v=4"},{"login":"Aimsucks","avatar_url":"https://avatars.githubusercontent.com/u/1828486?v=4"},{"login":"JannesV","avatar_url":"https://avatars.githubusercontent.com/u/5984244?v=4"},{"login":"MauriceNino","avatar_url":"https://avatars.githubusercontent.com/u/18658092?v=4"},{"login":"hillaliy","avatar_url":"https://avatars.githubusercontent.com/u/950010?v=4"},{"login":"Skeeww","avatar_url":"https://avatars.githubusercontent.com/u/9639992?v=4"},{"login":"RichyHBM","avatar_url":"https://avatars.githubusercontent.com/u/5472275?v=4"},{"login":"Bonfire","avatar_url":"https://avatars.githubusercontent.com/u/5704760?v=4"},{"login":"wieli99","avatar_url":"https://avatars.githubusercontent.com/u/46794825?v=4"},{"login":"ishaanparlikar","avatar_url":"https://avatars.githubusercontent.com/u/43013132?v=4"},{"login":"Zareix","avatar_url":"https://avatars.githubusercontent.com/u/29818713?v=4"},{"login":"tuggan","avatar_url":"https://avatars.githubusercontent.com/u/4325608?v=4"},{"login":"Spillebulle","avatar_url":"https://avatars.githubusercontent.com/u/46653946?v=4"},{"login":"tancak","avatar_url":"https://avatars.githubusercontent.com/u/11760749?v=4"},{"login":"spkesDE","avatar_url":"https://avatars.githubusercontent.com/u/1682314?v=4"},{"login":"j3lte","avatar_url":"https://avatars.githubusercontent.com/u/2557568?v=4"},{"login":"dslatt","avatar_url":"https://avatars.githubusercontent.com/u/5211001?v=4"},{"login":"Malong11-007","avatar_url":"https://avatars.githubusercontent.com/u/40298510?v=4"},{"login":"StefanB7","avatar_url":"https://avatars.githubusercontent.com/u/22056509?v=4"},{"login":"souravghosh01","avatar_url":"https://avatars.githubusercontent.com/u/37548139?v=4"},{"login":"SR3u","avatar_url":"https://avatars.githubusercontent.com/u/2380522?v=4"},{"login":"Rikpat","avatar_url":"https://avatars.githubusercontent.com/u/33869814?v=4"},{"login":"NormanJS","avatar_url":"https://avatars.githubusercontent.com/u/83409549?v=4"},{"login":"No-Maines-Land","avatar_url":"https://avatars.githubusercontent.com/u/20687725?v=4"},{"login":"stark1tty","avatar_url":"https://avatars.githubusercontent.com/u/69326376?v=4"},{"login":"Momcilo42","avatar_url":"https://avatars.githubusercontent.com/u/10118137?v=4"},{"login":"marius-arch","avatar_url":"https://avatars.githubusercontent.com/u/72494359?v=4"},{"login":"Lumilias","avatar_url":"https://avatars.githubusercontent.com/u/10852161?v=4"},{"login":"Havock94","avatar_url":"https://avatars.githubusercontent.com/u/7635248?v=4"},{"login":"Lollyde","avatar_url":"https://avatars.githubusercontent.com/u/11347524?v=4"},{"login":"krishnamuppaneni","avatar_url":"https://avatars.githubusercontent.com/u/4703366?v=4"},{"login":"devtobi","avatar_url":"https://avatars.githubusercontent.com/u/28538704?v=4"},{"login":"TyxTang","avatar_url":"https://avatars.githubusercontent.com/u/90897955?v=4"},{"login":"alotlikebeans","avatar_url":"https://avatars.githubusercontent.com/u/19630511?v=4"},{"login":"WillyJL","avatar_url":"https://avatars.githubusercontent.com/u/49810075?v=4"},{"login":"anonysoul","avatar_url":"https://avatars.githubusercontent.com/u/105715150?v=4"},{"login":"cospeedster","avatar_url":"https://avatars.githubusercontent.com/u/52749645?v=4"},{"login":"diederbert","avatar_url":"https://avatars.githubusercontent.com/u/162878798?v=4"},{"login":"digdilem","avatar_url":"https://avatars.githubusercontent.com/u/4052275?v=4"},{"login":"dinghino","avatar_url":"https://avatars.githubusercontent.com/u/14729459?v=4"},{"login":"gfomichev","avatar_url":"https://avatars.githubusercontent.com/u/1596146?v=4"},{"login":"gnattu","avatar_url":"https://avatars.githubusercontent.com/u/12995396?v=4"},{"login":"tuxpizza","avatar_url":"https://avatars.githubusercontent.com/u/84710786?v=4"},{"login":"xmarkurion","avatar_url":"https://avatars.githubusercontent.com/u/46654687?v=4"},{"login":"huanga","avatar_url":"https://avatars.githubusercontent.com/u/732970?v=4"},{"login":"taos15","avatar_url":"https://avatars.githubusercontent.com/u/122130728?v=4"},{"login":"arghyadipchak","avatar_url":"https://avatars.githubusercontent.com/u/32430374?v=4"},{"login":"AuthorShin","avatar_url":"https://avatars.githubusercontent.com/u/4959043?v=4"},{"login":"benniblot","avatar_url":"https://avatars.githubusercontent.com/u/47495914?v=4"},{"login":"bebsworthy","avatar_url":"https://avatars.githubusercontent.com/u/5266759?v=4"},{"login":"catrielmuller","avatar_url":"https://avatars.githubusercontent.com/u/2272323?v=4"},{"login":"Vysp3r","avatar_url":"https://avatars.githubusercontent.com/u/62019735?v=4"},{"login":"DR-Blogs","avatar_url":"https://avatars.githubusercontent.com/u/96393186?v=4"},{"login":"singular0","avatar_url":"https://avatars.githubusercontent.com/u/1925883?v=4"},{"login":"diogovalentte","avatar_url":"https://avatars.githubusercontent.com/u/49578155?v=4"},{"login":"codezninja","avatar_url":"https://avatars.githubusercontent.com/u/4400773?v=4"},{"login":"fabricionaweb","avatar_url":"https://avatars.githubusercontent.com/u/15933?v=4"},{"login":"nyok1912","avatar_url":"https://avatars.githubusercontent.com/u/32163949?v=4"},{"login":"bo3bdo","avatar_url":"https://avatars.githubusercontent.com/u/5271380?v=4"},{"login":"hskrtich","avatar_url":"https://avatars.githubusercontent.com/u/1214484?v=4"},{"login":"ishults","avatar_url":"https://avatars.githubusercontent.com/u/7275442?v=4"},{"login":"jelliuk","avatar_url":"https://avatars.githubusercontent.com/u/4790959?v=4"},{"login":"Moohan","avatar_url":"https://avatars.githubusercontent.com/u/5982260?v=4"},{"login":"tesar-tech","avatar_url":"https://avatars.githubusercontent.com/u/33880579?v=4"},{"login":"jbruell","avatar_url":"https://avatars.githubusercontent.com/u/31685172?v=4"},{"login":"JasonLeeB06","avatar_url":"https://avatars.githubusercontent.com/u/82266978?v=4"},{"login":"jeffersonraimon","avatar_url":"https://avatars.githubusercontent.com/u/80064475?v=4"},{"login":"jo-chemla","avatar_url":"https://avatars.githubusercontent.com/u/16822841?v=4"},{"login":"joser93","avatar_url":"https://avatars.githubusercontent.com/u/6912089?v=4"},{"login":"justijndepover","avatar_url":"https://avatars.githubusercontent.com/u/9008623?v=4"},{"login":"Aandree5","avatar_url":"https://avatars.githubusercontent.com/u/32734153?v=4"},{"login":"jackwellerreal","avatar_url":"https://avatars.githubusercontent.com/u/97435840?v=4"},{"login":"toineenzo","avatar_url":"https://avatars.githubusercontent.com/u/4921397?v=4"},{"login":"oben01","avatar_url":"https://avatars.githubusercontent.com/u/22369027?v=4"},{"login":"rezstje","avatar_url":"https://avatars.githubusercontent.com/u/45420315?v=4"},{"login":"castielwaverly","avatar_url":"https://avatars.githubusercontent.com/u/137005689?v=4"},{"login":"xoxyuxu","avatar_url":"https://avatars.githubusercontent.com/u/3543416?v=4"},{"login":"MaienM","avatar_url":"https://avatars.githubusercontent.com/u/583129?v=4"},{"login":"Copilot","avatar_url":"https://avatars.githubusercontent.com/in/1143301?v=4"},{"login":"tech62","avatar_url":"https://avatars.githubusercontent.com/u/16237642?v=4"},{"login":"pitschi","avatar_url":"https://avatars.githubusercontent.com/u/6841565?v=4"},{"login":"lthomsen","avatar_url":"https://avatars.githubusercontent.com/u/28560325?v=4"},{"login":"generalmat82","avatar_url":"https://avatars.githubusercontent.com/u/110256073?v=4"},{"login":"CrazyWolf13","avatar_url":"https://avatars.githubusercontent.com/u/96661824?v=4"},{"login":"noahcroghan","avatar_url":"https://avatars.githubusercontent.com/u/27422774?v=4"},{"login":"NikeLaosClericus","avatar_url":"https://avatars.githubusercontent.com/u/39810627?v=4"},{"login":"NicolasNewman","avatar_url":"https://avatars.githubusercontent.com/u/16135852?v=4"},{"login":"itzTheMeow","avatar_url":"https://avatars.githubusercontent.com/u/50887230?v=4"},{"login":"leandroscardua","avatar_url":"https://avatars.githubusercontent.com/u/24698541?v=4"},{"login":"hotrungnhan","avatar_url":"https://avatars.githubusercontent.com/u/40029631?v=4"},{"login":"Kaotic3","avatar_url":"https://avatars.githubusercontent.com/u/80522225?v=4"},{"login":"HeapReaper","avatar_url":"https://avatars.githubusercontent.com/u/31496522?v=4"},{"login":"frankvHoof93","avatar_url":"https://avatars.githubusercontent.com/u/7346516?v=4"},{"login":"faugusto-oliveira","avatar_url":"https://avatars.githubusercontent.com/u/37384074?v=4"},{"login":"ieugen","avatar_url":"https://avatars.githubusercontent.com/u/1050920?v=4"},{"login":"DZ945brH","avatar_url":"https://avatars.githubusercontent.com/u/42635243?v=4"},{"login":"Oupsman","avatar_url":"https://avatars.githubusercontent.com/u/11260343?v=4"},{"login":"m0s4ik","avatar_url":"https://avatars.githubusercontent.com/u/26578395?v=4"},{"login":"AartSchinkel","avatar_url":"https://avatars.githubusercontent.com/u/189675191?v=4"}] \ No newline at end of file diff --git a/static-data/translators.json b/static-data/translators.json index e6bcd15ed..c450e5cb5 100644 --- a/static-data/translators.json +++ b/static-data/translators.json @@ -1 +1 @@ -[{"username":"carlchina","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/6697/medium/d22bbe7797bbeb30dbdc73a5648d329a_default.png"},{"username":"zielmann","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/7795/medium/ad22b8b8d5eb33e4154d53a454c862fd_default.png"},{"username":"Thalyn","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12494028/medium/5faf5acc945a874ed116c4567edaa09f_default.png"},{"username":"magnushj","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12503488/medium/68f32e33ea7b891960055c9f831f7344_default.png"},{"username":"almontegil","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12580457/medium/f4136cacbdfdb4c28ae7f85dc5f840db_default.png"},{"username":"Skick","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12619811/medium/0331f1de413a645b4516bc4e037f31bb.jpg"},{"username":"andibing","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12664938/medium/b8be63e4dcb2e791ced1ffc9e3a049a5.jpg"},{"username":"gronare","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12680911/medium/c6eb14d1504156f34a158e507f73aa3d_default.png"},{"username":"SmartPhoneLover","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12701640/medium/ec95486662ec875cda080e778c3ff702.jpg"},{"username":"marcelotk_15","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12773227/medium/ecf6bea16ab0c873c131df2154ee9718_default.png"},{"username":"S3OD177","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13051544/medium/11dd990837f61f7e8f7d36b041a7fddb_default.png"},{"username":"marcg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13153301/medium/ebcf60fbf27deb148ec40a659566c6ff_default.png"},{"username":"BeersTeddy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13185230/medium/2f1f4e1effe74a23422b195cbefb2a95_default.png"},{"username":"jamesmcmahon0","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13245578/medium/586aa873b4abddbd9abc6f3de99ab70e.jpeg"},{"username":"b1nar10","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13286752/medium/249b02e3368752cb175446082015737c_default.png"},{"username":"vannCN","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13330448/medium/9b8a9ee3611e51e951e22d5fd4eb7d8d.jpg"},{"username":"binswm","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13343482/medium/45dde0e6097b9b72705d2eba9dbbc276_default.png"},{"username":"SkewRam","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13496556/medium/188f5c2deb7938eda51eb786cc4539ca.jpeg"},{"username":"raphcatarino","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13547726/medium/b003511e67df13a4b4b5689488fa8099.jpg"},{"username":"shillos5","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13600557/medium/b17650b2b84f829d54fa548c44c1ab27.jpeg"},{"username":"wolong98","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13641407/medium/f4634edc58c7857a357e5293543c15cf.jpg"},{"username":"spair0039","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14012333/medium/72430e96027c09c19141cac38eae4617.png"},{"username":"MajorMarcin","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14072183/medium/0a197e64bb92b6ea94cecbb607512c45.jpg"},{"username":"KosmoMoustache","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14103535/medium/51c438cdd9f86e8811c75f4869de479c.png"},{"username":"Soochaehwa","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14118689/medium/496a1ce63111547bf455a1e0a7ac75f1_default.png"},{"username":"garryfield","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14187035/medium/520c964706676045f6eeec1b08edc880.jpg"},{"username":"droidenko","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14444264/medium/4aa3a8e824f72bc9e5ec0d8de307095e.jpeg"},{"username":"maathis","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14489950/medium/6eeddf13a757ee35f78f1763f94d95e1.jpeg"},{"username":"bukvam","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14546502/medium/a819dc40a13c57395a17dc46e847d3ce.jpg"},{"username":"gm.cinalli","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14670666/medium/2d466a0fdbda40764526be86c97c0ab4.jpeg"},{"username":"antoine2tt","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14722148/medium/f88d926900862dd59007ea4b3419cb9d.png"},{"username":"cretzen","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14799754/medium/3ba5c1805ec20c000d5810c07985afe9.jpg"},{"username":"ktKongTong","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14817246/medium/97cfc3c028dbdaf85ebd1102da71e58c.jpeg"},{"username":"marinkaberg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14861042/medium/2d5c4e62613f03082f3e645fa92efd59.jpeg"},{"username":"djismgaming","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14934947/medium/f5a8570713c34ab0f7d5405d105e2a9a.jpeg"},{"username":"f1refa11","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14949159/medium/fd2ae63b8eb4462200ba96abf943c1b9.png"},{"username":"jeffersonraimon","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15057621/medium/6dff469563860e2618bc9d45646d0ab4.png"},{"username":"giop98","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15057987/medium/b8a4825d3fc39fc662f35ee258db4b2d.jpeg"},{"username":"tomislav.kraljevic","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15118857/medium/e133f1061cc92850b854d05d8faaeafd.png"},{"username":"Walkx","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15202182/medium/5c37361ae45aeed487b34582c1f7ca37.png"},{"username":"andrea.rosso","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15246318/medium/11f3f5ef44ec7f55b6f143090e208704_default.png"},{"username":"ajnart","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15270132/medium/a0f107a463c8910ee96bc2fa843a17e3.jpeg"},{"username":"Witchling","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15282238/medium/9e196b6783415b10ca571b7673c2ef33.jpg"},{"username":"guineuu","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15292058/medium/22fbb758bda3b7805d50bf21d38f2c20.jpeg"},{"username":"Bulgus","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15304568/medium/0e8787e5ceb02ed5c96a514d0068ae87.jpg"},{"username":"Narno","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15314996/medium/a6ba093f24884250a1bf6c6f2ca50f1b_default.png"},{"username":"BySempron","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15315986/medium/e3d22d7b1423c6823a9f36d595ed4bdb.png"},{"username":"jonathan.berglin.work","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15359236/medium/95930b2093db13b76179782f7322c5d5.png"},{"username":"icdmkg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15405504/medium/f334d16cde1ac4ffb935b440d03465af.gif"},{"username":"irithys","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15405614/medium/3086461c47cce0a0c031925e5f943412.png"},{"username":"JannesV","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15419912/medium/a7809eb4b817d7c49b62cf10ae86b950.png"},{"username":"benniblot","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15419914/medium/8086b1573d31f152ba41949b3a172445.jpeg"},{"username":"pacjo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15419916/medium/3cbeddbd7bc01faafb5a3bf47bba915b_default.png"},{"username":"Payou6994","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15419934/medium/37c9b9b17dfb578404c1c1ddb73ba7a8.png"},{"username":"fzibi21","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15419976/medium/0da688450358e0290a7b7359cc1f7328.png"},{"username":"WowMurdock","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15420118/medium/de19576fccb495b6dfe4c4c04a56b834.png"},{"username":"hbooo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15420120/medium/6c7c7f8db785061356ebb03d044d3329.jpeg"},{"username":"Manicraft1001","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15420178/medium/50ec94563a06a9f74f33bd09f01eed4d.jpg"},{"username":"Void123","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15420354/medium/86929d44df92a00f9fe900a985c196df_default.png"},{"username":"R4cc","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15422606/medium/a390979662b84694f59de30bdb732141.jpeg"},{"username":"fabricionaweb","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15425808/medium/a9354142e7ae5152c144409d55fda551_default.png"},{"username":"JokeOfDead","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15426890/medium/a383eac9365e9de64fd4ab1d6fd0cb95.jpeg"},{"username":"hkz","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15427174/medium/c88acefb0d7306e1f7470e872029fb39_default.png"},{"username":"Steken","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15428516/medium/d5aea5653c769c3a523182bdb60d1664.png"},{"username":"flar.anton","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15428592/medium/b01fdb365d892e9f811f77fcb50a80a2.jpeg"},{"username":"Bon","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15433542/medium/5397da4dfc821f20b6ac14fe0c514e9a.jpeg"},{"username":"bfkadan","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15434162/medium/4f9a0b43cfe3acaea60124c14ba7f44a.png"},{"username":"BunnySweety","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15434662/medium/f0ef200a6a0dcf0e1d0e9ecd4148f560_default.png"},{"username":"HRKings","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15436168/medium/69a31e461d38549f01864e7ef10d642e.png"},{"username":"wiston81","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15439078/medium/10f292c8d12a7c21a44b54495fa4a3d8.jpeg"},{"username":"qaz0911","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15440860/medium/3d3e50ee388c72dc4bf7a771761f2d89_default.png"},{"username":"qqyule","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15441462/medium/7a3cdf82710ffb5d8f388bc0bd010665.png"},{"username":"Bims0n","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15445560/medium/603220b603eeb3367e0f0d3fa675247c.jpg"},{"username":"TariqDaCoder","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15446228/medium/50b0f4040112bbd67690b769477398e5_default.png"},{"username":"Anarchon","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15449644/medium/e925e1f3e3ffbf0f982391ce263a1a28.jpeg"},{"username":"Ashun","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15453020/medium/ccdcf51c73d6aae40751bb30beee1915_default.png"},{"username":"sebekmartin","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15454038/medium/bcfb44598cdfd1d7cd4eb35812538962.jpeg"},{"username":"RagnarGraves","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15459882/medium/143d5af850c1154070a218bea124e9cb_default.png"},{"username":"PrtmPhlp","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15462414/medium/b80db55e9de301432dcd1f8c8b24fd49_default.png"},{"username":"DooYoo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15470768/medium/2a18cf4be67094724b508c9e1e698a21_default.png"},{"username":"espentruls","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15486092/medium/8e38afc3a4ff669226a0cfd3e420ff3a.jpeg"},{"username":"frisco82","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15486922/medium/4e1c5d4189b42508e660daa3c1c25b2a.jpeg"},{"username":"lupineDK","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15491798/medium/461bd501e8371c062bf29ea171aedd36_default.png"},{"username":"hillaliy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15492732/medium/0bae17b421604892d888e3fc70cf0587.jpeg"},{"username":"MarcOrfilaCarreras","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15501072/medium/ea52b26c3c6f21e4931e38e3ce3f3d6e.png"},{"username":"robertbridda","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15507822/medium/a368c2e30411bb2da9b49290084191f3.png"},{"username":"MKoniuszko","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15518090/medium/87605434fcc839f6763ab07c50f6d232.jpeg"},{"username":"HeroSizy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15518710/medium/4e79c0e98cbeb536dd961e656331b509.png"},{"username":"dwt136","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15520022/medium/554422503f2baea43ace85facb4546fb_default.png"},{"username":"asifthewebguy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15526719/medium/b18931dd0c800d725048bd440646198b_default.png"},{"username":"eiloogs","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15545537/medium/f290a2f1190983530a9b76b2e858a609.gif"},{"username":"_vytdv","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15547289/medium/60d8644cc8ad6f11a92ccea4a14cd098_default.png"},{"username":"crendasien","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15554645/medium/598ab1d4aaf6b8dccd5ba16be92da7b9.jpeg"},{"username":"edxo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15573823/medium/8a565b732a75a77f840dd123cdb30bf4.png"},{"username":"somerlev","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15617065/medium/f4b13513e311ec902d90b2f718412c55.jpg"},{"username":"kid1412621","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15643771/medium/dd455e32de652fa88e6fd97598bdffa7.png"},{"username":"suming","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15644717/medium/244159dfe10fa03436205506f80c9e25.png"},{"username":"nick.gher","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15647517/medium/5374a2c6fef60a3fbf0edf86b997c351_default.png"},{"username":"DataCat","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15650315/medium/ce7c1365adf35c5d490d77500a4607fb_default.png"},{"username":"dizo89","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15658375/medium/0ca745e5017d491fe1b22b0239904de8.jpeg"},{"username":"tee_noodle","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15674577/medium/804d9cf06f5196026acb6436b809d0da_default.png"},{"username":"Marty88","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15674593/medium/492b1509d52bd2809dea768121217125.jpeg"},{"username":"Spillebulle","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15677023/medium/20b98bb85062e07afe0e63618f8a5feb.PNG"},{"username":"petitmewen","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15685239/medium/15de9b62d2e0bc25013435f1784bbcc1.jpeg"},{"username":"NoProsNoNoobs","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15687709/medium/ae8f65fafeb8bcf74dcb8871bbe46461.png"},{"username":"y.gybson","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15690777/medium/b5cb5d2d5768904ff6586c727e3a6c77.jpeg"},{"username":"inside90","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15704947/medium/c1355fcb30dd76f8e39d98d1d49f1c52.png"},{"username":"RJSkudra","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15709853/medium/c3abf2774913dc4e81fb261d36d7668c.png"},{"username":"binge203","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15713937/medium/77c1cfa4314673db80e6881fd9f64668.gif"},{"username":"Mailootje","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15714337/medium/743c3bc4ab1989966a375eeeec83d8b8.jpeg"},{"username":"tim-wiegers","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15719805/medium/08a96f064813350661cd6b20bf3d7d99.png"},{"username":"GkhnG","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15722911/medium/71a027caec489ef6ce82bcf1888329d0_default.png"},{"username":"Zoen-Millo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15760967/medium/9e956f11adc5b34f5636268b5c485dbf.jpg"},{"username":"HooinKyoma","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15792897/medium/9489f0a9b368e0e827ae758b740a2eed.jpeg"},{"username":"wathergeen","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15794495/medium/a83b7fc80d180669ebfd6deb3544cfd0.png"},{"username":"MoeToo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15818233/medium/d35cd6953717706eaf20f6c143c62947.png"},{"username":"itodouble","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15836233/medium/e984caea18fb0673bf319fcf28cef649.png"},{"username":"loslocitos","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15847901/medium/fe30d12fd2cf38212f929e13b169f9ec.jpeg"},{"username":"Oversleep","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15849065/medium/06141f13a6d541d753f3c2f2947b8068_default.png"},{"username":"Beardy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15865139/medium/fca6b9d2b3f52e286d1568f52b83b6a0_default.png"},{"username":"raelyan","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15875457/medium/2f4fda1d1aaa5dcc79b328baf3f03151.jpeg"},{"username":"dolphin738","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15913763/medium/ee6fede7b8528ca642329ada80d1cc18.png"},{"username":"brunotco","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15916719/medium/09db45880fc05abc18adb8d932a5ecf9_default.png"},{"username":"LukeIsHereToDevelop","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15924739/medium/3e6cd3940297bb4ba7bb039c93e6f918.png"},{"username":"kennit","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15925879/medium/6b0733ad3c5949b91c55e4d8b03db8a5_default.png"},{"username":"kuunpire","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15950309/medium/8192a4f08f07086828ac9f74ed29a169.jpeg"},{"username":"Sandor-dev","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15951759/medium/0216c2da4eb028164ebbecf1c72f6271_default.png"},{"username":"Meierschlumpf","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15953187/medium/23c744faef1ab84fbdc9351a7850aab6.jpeg"},{"username":"harmlesscat","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15970733/medium/dd8d0214a0250c932bb518b1b55e45a2_default.png"},{"username":"OrzWTF","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15976121/medium/4c4557cbff7ff7b0503455bc59c020e0.jpeg"},{"username":"tagaishi","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15977271/medium/eade504c83a5a1ff831c80a538fbdb44_default.png"},{"username":"azurite928","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15981895/medium/547ccc459ee123e78b5401c499f1022d.png"},{"username":"Ronner231","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16021342/medium/7734d550df2de5a2fec2ffff33e7024c.jpeg"},{"username":"ugyes","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16034148/medium/ed001e3f470a2dea9a8ce955b18e7bd5.png"},{"username":"rpieja","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16045554/medium/bd55baca2ef8b92502a760cc9ee7c505_default.png"},{"username":"flambyisyou","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16051620/medium/3a3bc0c90f6b95ab4ef74396a0a17beb.png"},{"username":"Topbcy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16077170/medium/d3aed33ea56330338756cfcd89477cfe.jpeg"},{"username":"ai5d02sb","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16084674/medium/7c8119fe2a5ca71bb15f636916a42b95_default.png"},{"username":"explosiveparrot","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16097722/medium/7762f80fc1da63f5b2eb87de9d640324_default.png"},{"username":"DestroyCom","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16111544/medium/83e3d3ac5c8bfed634cd1a074854db67.png"},{"username":"neotraxo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16116966/medium/b30df957873329019a8a90a69f6efe3a.png"},{"username":"gzxpa","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16123314/medium/487b05577310a946b0b98bc72c11213b_default.png"},{"username":"TORIK","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16124732/medium/de770890e6411860ac06e8cc2dc0abd0_default.png"},{"username":"sergio.pibot","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16138114/medium/80db802b2fcd6bbf19ea4c7413367dca.jpeg"},{"username":"karam-ajaj","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16139838/medium/7539ab958d3c85d5f67e7849483ea8f7.jpeg"},{"username":"SimonHenz97","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16151520/medium/9b94a85b536688e2d94538c384fd0a76.png"},{"username":"olsson82","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16198988/medium/eeaab4cba210c537424d4204754b604e.png"},{"username":"bo3bdo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16222806/medium/8652f3beacb6098dde5a4cd0a29fe80d.jpeg"},{"username":"AngryPlayer04","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16245192/medium/342715274f4dc31fd4132cdae9126188.jpeg"},{"username":"ehatamm","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16245884/medium/979d5e5526fe545be9bf46db481f7182.jpeg"},{"username":"Balionelis","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16246058/medium/986d62ba363594c517d966fe9178dcbb.png"},{"username":"kwargs","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16248816/medium/19ce11f04bf1ae2637e0483399c675f9_default.png"},{"username":"lucicu","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16273892/medium/3b09e8f3bead221ef98d6a45a16851dc_default.png"},{"username":"Michael5564445","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16357688/medium/9d9026a8bf3eaccb0417da240932ebf7.png"},{"username":"Netscream","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16359198/medium/694e0c8572dc8a47e6d63da31ec5b992.png"},{"username":"belicstefan04","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16368142/medium/4742dcfd7bf7ad7e33e37ac44fba7e0f.png"},{"username":"Hamad2066","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16371478/medium/f37bbc7685b6757defdbd1494236af61.jpeg"},{"username":"3ct0pl4sm","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16399616/medium/ce796d1c40163975624c0a483e56374f.png"},{"username":"sanchez.marcell626","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16433865/medium/3ddde1314764cc2205ffdcd1ae719a91.png"},{"username":"Minoctis","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16490079/medium/91fd0849303a4eca033d660d0a7b6854.jpeg"},{"username":"vittoriosoccorso","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16498805/medium/17086b2f1981f734edd1572f79c3158a.jpeg"},{"username":"Worgen325","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16533177/medium/4a62cc6b06095751aeb4b1b3e96db93f.png"},{"username":"homarr","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16657003/medium/3afabfd3de949a54ff5d40ad04552aba.png"},{"username":"mwgg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16658379/medium/763dd09f5c238cc5324d4d5357985292.png"},{"username":"kiwinho","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16671855/medium/bfe3a2d497dc4748a04d02e4ae2139cb.jpeg"},{"username":"tjvg4m34r13","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16679135/medium/7a483b9b0abe0d925e54808e1cb38fa0.jpeg"},{"username":"Diegoem","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16705921/medium/29ce68ebc191fc3f65499511b12de98f_default.png"},{"username":"bouks69","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16746091/medium/b58b1fc08520de416198be9df5d093eb.jpeg"},{"username":"futhgar","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16748067/medium/b8c526b097f9aa9d687250cac3df16dc.jpeg"},{"username":"GrimSQL","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16764343/medium/fff729a76d1f8fbcdaee9bb0c0f5f3fd.png"},{"username":"chnikel","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16767047/medium/3dda2a37e03049dafefe6697bb00f75c.png"},{"username":"yuki19870616","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16767951/medium/f59c848e38382cda6c8464b0a7b9e642_default.png"},{"username":"noamdal","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16782245/medium/b4a72a61f9caf138f0322c0cb987e6d0.png"},{"username":"MalithRukshan","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16809057/medium/084d6a5b35fbbeacc70e7021197d222f.png"},{"username":"minirope19","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16930929/medium/e8cbbc26747e31c5a8959215e1dd067b.jpeg"},{"username":"toineenzo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12371852/medium/9f1b2bedb4794c59479cd289bdba9fab.png"},{"username":"ArcaneWater","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12421105/medium/1ac82517a6c658f6b62c48aebcb17e86_default.png"},{"username":"Ebedami","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12974801/medium/b596dea5c09454c4c828eac558389755.jpg"},{"username":"SergiSvK","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13320543/medium/3df3c79cc5f0c53716c559cd3fd2fc20.png"},{"username":"jeongeon","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13362856/medium/eb572f72cde5e5526c517ca3a6489850_default.png"},{"username":"MrTranCFCVN","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13439475/medium/a1789d69c85d90c667194b5d6a518c08_default.png"},{"username":"Cruellest","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13610615/medium/2a74faa0947ee08d18f7946fb8602c1e.jpg"},{"username":"Thario","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14491372/medium/dd5e142070802a672900121aa3f332a6_default.png"},{"username":"albanobattistella","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14686194/medium/564afcfd76f5cd24751bd669cf4943aa.jpg"},{"username":"Kachelkaiser","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14763406/medium/203a781797da4a18fe0f4e7db8fd547c.png"},{"username":"regi4","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14842936/medium/f7e1d672f562a7db61ba6b4b8f4de41d.png"},{"username":"baton5129","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14891064/medium/8d438f557b82977dd02f0ead3187265f.PNG"},{"username":"meviper","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15958903/medium/209f521ce33a05831fa8d7d1b7babc20_default.png"},{"username":"TheRedCyclops","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16486399/medium/7f39226180120d5a9efcb8e13d8aec8e_default.png"},{"username":"dymek37","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16659221/medium/d1dc6d11d7e7fc370efc5070e837ae03.png"},{"username":"B3CKDOOR","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16744975/medium/e6c7b2d45e76465d54e6efb67c83da7d.png"},{"username":"Larsluph","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16779933/medium/ed7d36f8b44013bad3a16ac0491bd0f7.png"},{"username":"FOEDI","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16781001/medium/6b4552fb72e359aa016f7a054078fac8.png"},{"username":"xmarkurion","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16805443/medium/34a20428427de5aaef99c7d140646803.png"},{"username":"gregid","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16806705/medium/f3c8f4a20436e8538929130142bbc104.png"},{"username":"Flameberg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16820453/medium/d47be866148474c9d05fec6fbe406d88.png"},{"username":"Playfust","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16872865/medium/5f4609cc711e5fb9fa8a0b178635769c.jpeg"},{"username":"Cotignac","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16879297/medium/81ff353edf030699a159bb79cda26e84.jpg"},{"username":"LeaHun","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16916375/medium/618520f0543b1e212ebc83d66b683d4b_default.png"},{"username":"jackwellerreal","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16919717/medium/37bbfdc03893f141133ccc619593266d.png"},{"username":"andrius.cim","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16967845/medium/46028fc1a0ddfaaeebcb37b39739625d_default.png"},{"username":"xjxexrxrxyx","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17041412/medium/d34d664166db6fb25579bca9ff3b50b4.jpeg"},{"username":"bilhal.fellah","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17044572/medium/d1ec6f17e4aa013e9eededc83778d2c6.png"},{"username":"the_octo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17058060/medium/7423ab3a4cabb67f5decb2e2dfa2b675_default.png"},{"username":"BamesJond","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17075846/medium/128d7ffd40254c8e376f45678e2fc620_default.png"},{"username":"Kornikus","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17134240/medium/d7a42b2c30db46ff152699ca01f9e11a_default.png"},{"username":"xoxyuxu","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17176526/medium/108055aa24343043de852e412bec74be.png"},{"username":"cryptodntsleep","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17249408/medium/b9e15aa2b24a0dbc1e4f194aa8ed5109.png"},{"username":"yaniorg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17261842/medium/7dbfc77f21c826845961243c44540d26.jpg"},{"username":"Vidariondr","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17262438/medium/45fbc71be02ef27296b6f423e97b6a03_default.png"},{"username":"Fastery","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17263066/medium/701b0edc59676174863ad651528bbb37.jpeg"},{"username":"stratself","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17290388/medium/57d9d8161b3fd0abf4dc5558b4a1fa05_default.png"},{"username":"wuzeqin","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17292606/medium/b9e0308410b31337e2d7aa6ed346d718_default.png"},{"username":"ChristopherBull","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17304338/medium/21a175ce596bfae81430767467908b90.jpeg"},{"username":"darkbcnlord","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17311436/medium/b271bc0b419ec3a26ddb7b63ff8ffc4b.png"},{"username":"zhoran1997","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17344028/medium/c8f450b5a8f1e9494a45dc60af7ce63e.png"},{"username":"gilalves1703","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17447768/medium/3f1de562a5c2391c88bbf8d5cf40a266.png"}] \ No newline at end of file +[{"username":"carlchina","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/6697/medium/d22bbe7797bbeb30dbdc73a5648d329a_default.png"},{"username":"zielmann","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/7795/medium/ad22b8b8d5eb33e4154d53a454c862fd_default.png"},{"username":"Thalyn","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12494028/medium/5faf5acc945a874ed116c4567edaa09f_default.png"},{"username":"magnushj","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12503488/medium/68f32e33ea7b891960055c9f831f7344_default.png"},{"username":"almontegil","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12580457/medium/f4136cacbdfdb4c28ae7f85dc5f840db_default.png"},{"username":"Skick","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12619811/medium/0331f1de413a645b4516bc4e037f31bb.jpg"},{"username":"andibing","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12664938/medium/b8be63e4dcb2e791ced1ffc9e3a049a5.jpg"},{"username":"gronare","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12680911/medium/c6eb14d1504156f34a158e507f73aa3d_default.png"},{"username":"SmartPhoneLover","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12701640/medium/ec95486662ec875cda080e778c3ff702.jpg"},{"username":"marcelotk_15","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12773227/medium/ecf6bea16ab0c873c131df2154ee9718_default.png"},{"username":"S3OD177","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13051544/medium/11dd990837f61f7e8f7d36b041a7fddb_default.png"},{"username":"marcg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13153301/medium/ebcf60fbf27deb148ec40a659566c6ff_default.png"},{"username":"BeersTeddy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13185230/medium/2f1f4e1effe74a23422b195cbefb2a95_default.png"},{"username":"jamesmcmahon0","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13245578/medium/586aa873b4abddbd9abc6f3de99ab70e.jpeg"},{"username":"b1nar10","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13286752/medium/249b02e3368752cb175446082015737c_default.png"},{"username":"vannCN","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13330448/medium/9b8a9ee3611e51e951e22d5fd4eb7d8d.jpg"},{"username":"binswm","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13343482/medium/45dde0e6097b9b72705d2eba9dbbc276_default.png"},{"username":"SkewRam","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13496556/medium/188f5c2deb7938eda51eb786cc4539ca.jpeg"},{"username":"raphcatarino","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13547726/medium/b003511e67df13a4b4b5689488fa8099.jpg"},{"username":"shillos5","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13600557/medium/b17650b2b84f829d54fa548c44c1ab27.jpeg"},{"username":"wolong98","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13641407/medium/f4634edc58c7857a357e5293543c15cf.jpg"},{"username":"spair0039","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14012333/medium/72430e96027c09c19141cac38eae4617.png"},{"username":"MajorMarcin","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14072183/medium/0a197e64bb92b6ea94cecbb607512c45.jpg"},{"username":"KosmoMoustache","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14103535/medium/51c438cdd9f86e8811c75f4869de479c.png"},{"username":"Soochaehwa","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14118689/medium/496a1ce63111547bf455a1e0a7ac75f1_default.png"},{"username":"garryfield","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14187035/medium/520c964706676045f6eeec1b08edc880.jpg"},{"username":"droidenko","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14444264/medium/4aa3a8e824f72bc9e5ec0d8de307095e.jpeg"},{"username":"maathis","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14489950/medium/6eeddf13a757ee35f78f1763f94d95e1.jpeg"},{"username":"bukvam","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14546502/medium/a819dc40a13c57395a17dc46e847d3ce.jpg"},{"username":"gm.cinalli","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14670666/medium/2d466a0fdbda40764526be86c97c0ab4.jpeg"},{"username":"antoine2tt","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14722148/medium/f88d926900862dd59007ea4b3419cb9d.png"},{"username":"cretzen","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14799754/medium/3ba5c1805ec20c000d5810c07985afe9.jpg"},{"username":"ktKongTong","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14817246/medium/97cfc3c028dbdaf85ebd1102da71e58c.jpeg"},{"username":"marinkaberg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14861042/medium/2d5c4e62613f03082f3e645fa92efd59.jpeg"},{"username":"djismgaming","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14934947/medium/f5a8570713c34ab0f7d5405d105e2a9a.jpeg"},{"username":"f1refa11","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14949159/medium/fd2ae63b8eb4462200ba96abf943c1b9.png"},{"username":"jeffersonraimon","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15057621/medium/6dff469563860e2618bc9d45646d0ab4.png"},{"username":"giop98","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15057987/medium/b8a4825d3fc39fc662f35ee258db4b2d.jpeg"},{"username":"tomislav.kraljevic","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15118857/medium/e133f1061cc92850b854d05d8faaeafd.png"},{"username":"Walkx","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15202182/medium/5c37361ae45aeed487b34582c1f7ca37.png"},{"username":"andrea.rosso","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15246318/medium/11f3f5ef44ec7f55b6f143090e208704_default.png"},{"username":"ajnart","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15270132/medium/a0f107a463c8910ee96bc2fa843a17e3.jpeg"},{"username":"Witchling","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15282238/medium/9e196b6783415b10ca571b7673c2ef33.jpg"},{"username":"guineuu","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15292058/medium/22fbb758bda3b7805d50bf21d38f2c20.jpeg"},{"username":"Bulgus","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15304568/medium/0e8787e5ceb02ed5c96a514d0068ae87.jpg"},{"username":"Narno","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15314996/medium/a6ba093f24884250a1bf6c6f2ca50f1b_default.png"},{"username":"BySempron","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15315986/medium/e3d22d7b1423c6823a9f36d595ed4bdb.png"},{"username":"jonathan.berglin.work","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15359236/medium/95930b2093db13b76179782f7322c5d5.png"},{"username":"icdmkg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15405504/medium/f334d16cde1ac4ffb935b440d03465af.gif"},{"username":"irithys","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15405614/medium/3086461c47cce0a0c031925e5f943412.png"},{"username":"JannesV","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15419912/medium/a7809eb4b817d7c49b62cf10ae86b950.png"},{"username":"benniblot","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15419914/medium/8086b1573d31f152ba41949b3a172445.jpeg"},{"username":"pacjo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15419916/medium/3cbeddbd7bc01faafb5a3bf47bba915b_default.png"},{"username":"Payou6994","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15419934/medium/37c9b9b17dfb578404c1c1ddb73ba7a8.png"},{"username":"fzibi21","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15419976/medium/0da688450358e0290a7b7359cc1f7328.png"},{"username":"WowMurdock","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15420118/medium/de19576fccb495b6dfe4c4c04a56b834.png"},{"username":"hbooo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15420120/medium/6c7c7f8db785061356ebb03d044d3329.jpeg"},{"username":"Manicraft1001","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15420178/medium/50ec94563a06a9f74f33bd09f01eed4d.jpg"},{"username":"Void123","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15420354/medium/86929d44df92a00f9fe900a985c196df_default.png"},{"username":"R4cc","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15422606/medium/a390979662b84694f59de30bdb732141.jpeg"},{"username":"fabricionaweb","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15425808/medium/a9354142e7ae5152c144409d55fda551_default.png"},{"username":"JokeOfDead","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15426890/medium/a383eac9365e9de64fd4ab1d6fd0cb95.jpeg"},{"username":"hkz","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15427174/medium/c88acefb0d7306e1f7470e872029fb39_default.png"},{"username":"Steken","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15428516/medium/d5aea5653c769c3a523182bdb60d1664.png"},{"username":"flar.anton","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15428592/medium/b01fdb365d892e9f811f77fcb50a80a2.jpeg"},{"username":"Bon","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15433542/medium/5397da4dfc821f20b6ac14fe0c514e9a.jpeg"},{"username":"bfkadan","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15434162/medium/4f9a0b43cfe3acaea60124c14ba7f44a.png"},{"username":"BunnySweety","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15434662/medium/f0ef200a6a0dcf0e1d0e9ecd4148f560_default.png"},{"username":"HRKings","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15436168/medium/69a31e461d38549f01864e7ef10d642e.png"},{"username":"wiston81","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15439078/medium/10f292c8d12a7c21a44b54495fa4a3d8.jpeg"},{"username":"qaz0911","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15440860/medium/3d3e50ee388c72dc4bf7a771761f2d89_default.png"},{"username":"qqyule","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15441462/medium/7a3cdf82710ffb5d8f388bc0bd010665.png"},{"username":"Bims0n","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15445560/medium/603220b603eeb3367e0f0d3fa675247c.jpg"},{"username":"TariqDaCoder","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15446228/medium/50b0f4040112bbd67690b769477398e5_default.png"},{"username":"Anarchon","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15449644/medium/e925e1f3e3ffbf0f982391ce263a1a28.jpeg"},{"username":"Ashun","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15453020/medium/ccdcf51c73d6aae40751bb30beee1915_default.png"},{"username":"sebekmartin","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15454038/medium/bcfb44598cdfd1d7cd4eb35812538962.jpeg"},{"username":"RagnarGraves","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15459882/medium/143d5af850c1154070a218bea124e9cb_default.png"},{"username":"PrtmPhlp","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15462414/medium/b80db55e9de301432dcd1f8c8b24fd49_default.png"},{"username":"DooYoo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15470768/medium/2a18cf4be67094724b508c9e1e698a21_default.png"},{"username":"espentruls","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15486092/medium/8e38afc3a4ff669226a0cfd3e420ff3a.jpeg"},{"username":"frisco82","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15486922/medium/4e1c5d4189b42508e660daa3c1c25b2a.jpeg"},{"username":"lupineDK","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15491798/medium/461bd501e8371c062bf29ea171aedd36_default.png"},{"username":"hillaliy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15492732/medium/0bae17b421604892d888e3fc70cf0587.jpeg"},{"username":"MarcOrfilaCarreras","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15501072/medium/ea52b26c3c6f21e4931e38e3ce3f3d6e.png"},{"username":"robertbridda","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15507822/medium/a368c2e30411bb2da9b49290084191f3.png"},{"username":"MKoniuszko","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15518090/medium/87605434fcc839f6763ab07c50f6d232.jpeg"},{"username":"HeroSizy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15518710/medium/4e79c0e98cbeb536dd961e656331b509.png"},{"username":"dwt136","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15520022/medium/554422503f2baea43ace85facb4546fb_default.png"},{"username":"asifthewebguy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15526719/medium/b18931dd0c800d725048bd440646198b_default.png"},{"username":"eiloogs","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15545537/medium/f290a2f1190983530a9b76b2e858a609.gif"},{"username":"_vytdv","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15547289/medium/60d8644cc8ad6f11a92ccea4a14cd098_default.png"},{"username":"crendasien","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15554645/medium/598ab1d4aaf6b8dccd5ba16be92da7b9.jpeg"},{"username":"edxo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15573823/medium/8a565b732a75a77f840dd123cdb30bf4.png"},{"username":"somerlev","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15617065/medium/f4b13513e311ec902d90b2f718412c55.jpg"},{"username":"kid1412621","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15643771/medium/dd455e32de652fa88e6fd97598bdffa7.png"},{"username":"suming","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15644717/medium/244159dfe10fa03436205506f80c9e25.png"},{"username":"nick.gher","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15647517/medium/5374a2c6fef60a3fbf0edf86b997c351_default.png"},{"username":"DataCat","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15650315/medium/ce7c1365adf35c5d490d77500a4607fb_default.png"},{"username":"dizo89","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15658375/medium/0ca745e5017d491fe1b22b0239904de8.jpeg"},{"username":"tee_noodle","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15674577/medium/804d9cf06f5196026acb6436b809d0da_default.png"},{"username":"Marty88","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15674593/medium/492b1509d52bd2809dea768121217125.jpeg"},{"username":"Spillebulle","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15677023/medium/20b98bb85062e07afe0e63618f8a5feb.PNG"},{"username":"petitmewen","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15685239/medium/15de9b62d2e0bc25013435f1784bbcc1.jpeg"},{"username":"NoProsNoNoobs","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15687709/medium/ae8f65fafeb8bcf74dcb8871bbe46461.png"},{"username":"y.gybson","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15690777/medium/b5cb5d2d5768904ff6586c727e3a6c77.jpeg"},{"username":"inside90","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15704947/medium/c1355fcb30dd76f8e39d98d1d49f1c52.png"},{"username":"RJSkudra","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15709853/medium/c3abf2774913dc4e81fb261d36d7668c.png"},{"username":"binge203","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15713937/medium/77c1cfa4314673db80e6881fd9f64668.gif"},{"username":"Mailootje","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15714337/medium/743c3bc4ab1989966a375eeeec83d8b8.jpeg"},{"username":"tim-wiegers","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15719805/medium/08a96f064813350661cd6b20bf3d7d99.png"},{"username":"GkhnG","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15722911/medium/71a027caec489ef6ce82bcf1888329d0_default.png"},{"username":"Zoen-Millo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15760967/medium/9e956f11adc5b34f5636268b5c485dbf.jpg"},{"username":"HooinKyoma","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15792897/medium/9489f0a9b368e0e827ae758b740a2eed.jpeg"},{"username":"wathergeen","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15794495/medium/a83b7fc80d180669ebfd6deb3544cfd0.png"},{"username":"MoeToo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15818233/medium/d35cd6953717706eaf20f6c143c62947.png"},{"username":"itodouble","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15836233/medium/e984caea18fb0673bf319fcf28cef649.png"},{"username":"loslocitos","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15847901/medium/fe30d12fd2cf38212f929e13b169f9ec.jpeg"},{"username":"Oversleep","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15849065/medium/06141f13a6d541d753f3c2f2947b8068_default.png"},{"username":"Beardy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15865139/medium/fca6b9d2b3f52e286d1568f52b83b6a0_default.png"},{"username":"raelyan","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15875457/medium/2f4fda1d1aaa5dcc79b328baf3f03151.jpeg"},{"username":"dolphin738","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15913763/medium/ee6fede7b8528ca642329ada80d1cc18.png"},{"username":"brunotco","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15916719/medium/09db45880fc05abc18adb8d932a5ecf9_default.png"},{"username":"LukeIsHereToDevelop","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15924739/medium/3e6cd3940297bb4ba7bb039c93e6f918.png"},{"username":"kennit","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15925879/medium/6b0733ad3c5949b91c55e4d8b03db8a5_default.png"},{"username":"kuunpire","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15950309/medium/8192a4f08f07086828ac9f74ed29a169.jpeg"},{"username":"Sandor-dev","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15951759/medium/0216c2da4eb028164ebbecf1c72f6271_default.png"},{"username":"Meierschlumpf","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15953187/medium/23c744faef1ab84fbdc9351a7850aab6.jpeg"},{"username":"harmlesscat","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15970733/medium/dd8d0214a0250c932bb518b1b55e45a2_default.png"},{"username":"OrzWTF","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15976121/medium/4c4557cbff7ff7b0503455bc59c020e0.jpeg"},{"username":"tagaishi","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15977271/medium/eade504c83a5a1ff831c80a538fbdb44_default.png"},{"username":"azurite928","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15981895/medium/547ccc459ee123e78b5401c499f1022d.png"},{"username":"Ronner231","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16021342/medium/7734d550df2de5a2fec2ffff33e7024c.jpeg"},{"username":"ugyes","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16034148/medium/ed001e3f470a2dea9a8ce955b18e7bd5.png"},{"username":"rpieja","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16045554/medium/bd55baca2ef8b92502a760cc9ee7c505_default.png"},{"username":"flambyisyou","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16051620/medium/3a3bc0c90f6b95ab4ef74396a0a17beb.png"},{"username":"Topbcy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16077170/medium/d3aed33ea56330338756cfcd89477cfe.jpeg"},{"username":"ai5d02sb","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16084674/medium/7c8119fe2a5ca71bb15f636916a42b95_default.png"},{"username":"explosiveparrot","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16097722/medium/7762f80fc1da63f5b2eb87de9d640324_default.png"},{"username":"DestroyCom","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16111544/medium/83e3d3ac5c8bfed634cd1a074854db67.png"},{"username":"neotraxo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16116966/medium/b30df957873329019a8a90a69f6efe3a.png"},{"username":"gzxpa","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16123314/medium/487b05577310a946b0b98bc72c11213b_default.png"},{"username":"TORIK","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16124732/medium/de770890e6411860ac06e8cc2dc0abd0_default.png"},{"username":"sergio.pibot","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16138114/medium/80db802b2fcd6bbf19ea4c7413367dca.jpeg"},{"username":"karam-ajaj","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16139838/medium/7539ab958d3c85d5f67e7849483ea8f7.jpeg"},{"username":"SimonHenz97","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16151520/medium/9b94a85b536688e2d94538c384fd0a76.png"},{"username":"olsson82","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16198988/medium/eeaab4cba210c537424d4204754b604e.png"},{"username":"bo3bdo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16222806/medium/8652f3beacb6098dde5a4cd0a29fe80d.jpeg"},{"username":"AngryPlayer04","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16245192/medium/342715274f4dc31fd4132cdae9126188.jpeg"},{"username":"ehatamm","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16245884/medium/979d5e5526fe545be9bf46db481f7182.jpeg"},{"username":"Balionelis","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16246058/medium/986d62ba363594c517d966fe9178dcbb.png"},{"username":"kwargs","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16248816/medium/19ce11f04bf1ae2637e0483399c675f9_default.png"},{"username":"lucicu","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16273892/medium/3b09e8f3bead221ef98d6a45a16851dc_default.png"},{"username":"Michael5564445","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16357688/medium/9d9026a8bf3eaccb0417da240932ebf7.png"},{"username":"Netscream","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16359198/medium/694e0c8572dc8a47e6d63da31ec5b992.png"},{"username":"belicstefan04","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16368142/medium/4742dcfd7bf7ad7e33e37ac44fba7e0f.png"},{"username":"Hamad2066","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16371478/medium/f37bbc7685b6757defdbd1494236af61.jpeg"},{"username":"3ct0pl4sm","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16399616/medium/ce796d1c40163975624c0a483e56374f.png"},{"username":"sanchez.marcell626","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16433865/medium/3ddde1314764cc2205ffdcd1ae719a91.png"},{"username":"Minoctis","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16490079/medium/91fd0849303a4eca033d660d0a7b6854.jpeg"},{"username":"vittoriosoccorso","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16498805/medium/17086b2f1981f734edd1572f79c3158a.jpeg"},{"username":"Worgen325","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16533177/medium/4a62cc6b06095751aeb4b1b3e96db93f.png"},{"username":"homarr","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16657003/medium/3afabfd3de949a54ff5d40ad04552aba.png"},{"username":"mwgg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16658379/medium/763dd09f5c238cc5324d4d5357985292.png"},{"username":"kiwinho","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16671855/medium/bfe3a2d497dc4748a04d02e4ae2139cb.jpeg"},{"username":"tjvg4m34r13","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16679135/medium/7a483b9b0abe0d925e54808e1cb38fa0.jpeg"},{"username":"Diegoem","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16705921/medium/29ce68ebc191fc3f65499511b12de98f_default.png"},{"username":"bouks69","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16746091/medium/b58b1fc08520de416198be9df5d093eb.jpeg"},{"username":"futhgar","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16748067/medium/b8c526b097f9aa9d687250cac3df16dc.jpeg"},{"username":"GrimSQL","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16764343/medium/fff729a76d1f8fbcdaee9bb0c0f5f3fd.png"},{"username":"chnikel","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16767047/medium/3dda2a37e03049dafefe6697bb00f75c.png"},{"username":"yuki19870616","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16767951/medium/f59c848e38382cda6c8464b0a7b9e642_default.png"},{"username":"noamdal","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16782245/medium/b4a72a61f9caf138f0322c0cb987e6d0.png"},{"username":"MalithRukshan","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16809057/medium/084d6a5b35fbbeacc70e7021197d222f.png"},{"username":"minirope19","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16930929/medium/e8cbbc26747e31c5a8959215e1dd067b.jpeg"},{"username":"toineenzo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12371852/medium/9f1b2bedb4794c59479cd289bdba9fab.png"},{"username":"ArcaneWater","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12421105/medium/1ac82517a6c658f6b62c48aebcb17e86_default.png"},{"username":"Ebedami","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12974801/medium/b596dea5c09454c4c828eac558389755.jpg"},{"username":"SergiSvK","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13320543/medium/3df3c79cc5f0c53716c559cd3fd2fc20.png"},{"username":"jeongeon","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13362856/medium/eb572f72cde5e5526c517ca3a6489850_default.png"},{"username":"MrTranCFCVN","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13439475/medium/a1789d69c85d90c667194b5d6a518c08_default.png"},{"username":"Cruellest","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13610615/medium/2a74faa0947ee08d18f7946fb8602c1e.jpg"},{"username":"Thario","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14491372/medium/dd5e142070802a672900121aa3f332a6_default.png"},{"username":"albanobattistella","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14686194/medium/564afcfd76f5cd24751bd669cf4943aa.jpg"},{"username":"Kachelkaiser","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14763406/medium/203a781797da4a18fe0f4e7db8fd547c.png"},{"username":"regi4","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14842936/medium/f7e1d672f562a7db61ba6b4b8f4de41d.png"},{"username":"baton5129","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14891064/medium/8d438f557b82977dd02f0ead3187265f.PNG"},{"username":"meviper","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15958903/medium/209f521ce33a05831fa8d7d1b7babc20_default.png"},{"username":"TheRedCyclops","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16486399/medium/7f39226180120d5a9efcb8e13d8aec8e_default.png"},{"username":"dymek37","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16659221/medium/d1dc6d11d7e7fc370efc5070e837ae03.png"},{"username":"B3CKDOOR","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16744975/medium/e6c7b2d45e76465d54e6efb67c83da7d.png"},{"username":"Larsluph","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16779933/medium/ed7d36f8b44013bad3a16ac0491bd0f7.png"},{"username":"FOEDI","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16781001/medium/6b4552fb72e359aa016f7a054078fac8.png"},{"username":"xmarkurion","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16805443/medium/34a20428427de5aaef99c7d140646803.png"},{"username":"gregid","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16806705/medium/f3c8f4a20436e8538929130142bbc104.png"},{"username":"Flameberg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16820453/medium/d47be866148474c9d05fec6fbe406d88.png"},{"username":"Playfust","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16872865/medium/5f4609cc711e5fb9fa8a0b178635769c.jpeg"},{"username":"Cotignac","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16879297/medium/81ff353edf030699a159bb79cda26e84.jpg"},{"username":"LeaHun","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16916375/medium/618520f0543b1e212ebc83d66b683d4b_default.png"},{"username":"jackwellerreal","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16919717/medium/37bbfdc03893f141133ccc619593266d.png"},{"username":"andrius.cim","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16967845/medium/46028fc1a0ddfaaeebcb37b39739625d_default.png"},{"username":"xjxexrxrxyx","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17041412/medium/d34d664166db6fb25579bca9ff3b50b4.jpeg"},{"username":"bilhal.fellah","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17044572/medium/d1ec6f17e4aa013e9eededc83778d2c6.png"},{"username":"the_octo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17058060/medium/7423ab3a4cabb67f5decb2e2dfa2b675_default.png"},{"username":"BamesJond","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17075846/medium/128d7ffd40254c8e376f45678e2fc620_default.png"},{"username":"Kornikus","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17134240/medium/d7a42b2c30db46ff152699ca01f9e11a_default.png"},{"username":"xoxyuxu","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17176526/medium/108055aa24343043de852e412bec74be.png"},{"username":"cryptodntsleep","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17249408/medium/b9e15aa2b24a0dbc1e4f194aa8ed5109.png"},{"username":"yaniorg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17261842/medium/7dbfc77f21c826845961243c44540d26.jpg"},{"username":"Vidariondr","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17262438/medium/45fbc71be02ef27296b6f423e97b6a03_default.png"},{"username":"Fastery","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17263066/medium/701b0edc59676174863ad651528bbb37.jpeg"},{"username":"stratself","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17290388/medium/57d9d8161b3fd0abf4dc5558b4a1fa05_default.png"},{"username":"wuzeqin","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17292606/medium/b9e0308410b31337e2d7aa6ed346d718_default.png"},{"username":"ChristopherBull","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17304338/medium/21a175ce596bfae81430767467908b90.jpeg"},{"username":"darkbcnlord","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17311436/medium/b271bc0b419ec3a26ddb7b63ff8ffc4b.png"},{"username":"zhoran1997","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17344028/medium/c8f450b5a8f1e9494a45dc60af7ce63e.png"},{"username":"gilalves1703","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17447768/medium/3f1de562a5c2391c88bbf8d5cf40a266.png"},{"username":"iServeCloud","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17470960/medium/11649b99d609438d66455a4d2fdc70f7.png"},{"username":"guibolzantvbox","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17474136/medium/59a576e6da538664ceb972253916fa02.jpeg"}] \ No newline at end of file diff --git a/tooling/eslint/package.json b/tooling/eslint/package.json index 7e8dbcc54..f5deebad2 100644 --- a/tooling/eslint/package.json +++ b/tooling/eslint/package.json @@ -17,19 +17,19 @@ }, "prettier": "@homarr/prettier-config", "dependencies": { - "@next/eslint-plugin-next": "16.0.7", + "@next/eslint-plugin-next": "16.0.10", "eslint-config-prettier": "^10.1.8", "eslint-config-turbo": "^2.6.3", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^6.1.1", - "typescript-eslint": "^8.48.1" + "typescript-eslint": "^8.49.0" }, "devDependencies": { "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "typescript": "^5.9.3" } } diff --git a/tooling/github/setup/action.yml b/tooling/github/setup/action.yml index 0aa10f762..71c2fab10 100644 --- a/tooling/github/setup/action.yml +++ b/tooling/github/setup/action.yml @@ -7,7 +7,7 @@ runs: - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v6 with: - node-version: 24.11.1 + node-version: 24.12.0 cache: "pnpm" - shell: bash diff --git a/tooling/typescript/base.json b/tooling/typescript/base.json index 4ff98d150..ea9ea793b 100644 --- a/tooling/typescript/base.json +++ b/tooling/typescript/base.json @@ -1,8 +1,8 @@ { "$schema": "https://json.schemastore.org/tsconfig", "compilerOptions": { - "target": "ES2022", - "lib": ["dom", "dom.iterable", "ES2022"], + "target": "ES2024", + "lib": ["dom", "dom.iterable", "ES2024"], "allowJs": true, "skipLibCheck": true, "strict": true,