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 index 2f5fe835a..4751c9029 100644 --- a/.github/actions/extract-build-artifact/action.yaml +++ b/.github/actions/extract-build-artifact/action.yaml @@ -5,7 +5,7 @@ inputs: description: Digest of Docker image to use required: true architecture: - description: Name of architecture, will be used to create directories (e.g. amd64, arm64) + 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 @@ -19,25 +19,46 @@ inputs: runs: using: "composite" steps: + - name: Prebuilt debian dependencies + uses: homarr-labs/homarr/.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: Extract build from ${{ inputs.architecture }} container + - name: Prepare extraction run: | docker exec homarr cp /etc/nginx/templates/nginx.conf /app && \ - docker exec homarr tar -czf extraction.tar.gz -C /app . && \ - mkdir -p ${{ runner.temp }}/extraction/${{ inputs.architecture }} && \ - docker cp homarr:/app/extraction.tar.gz ${{ runner.temp }}/extraction/${{ inputs.architecture }}/build-${{ inputs.architecture }}.tar.gz + 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 + - 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-${{ inputs.architecture }}.tar.gz --clobber + 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 1f9f40f17..8335bb46c 100644 --- a/.github/workflows/deployment-docker-image.yml +++ b/.github/workflows/deployment-docker-image.yml @@ -32,6 +32,7 @@ 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: @@ -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: @@ -150,6 +152,7 @@ jobs: name: Build docker image for arm64 needs: release runs-on: ubuntu-24.04-arm + timeout-minutes: 20 outputs: digest: ${{ steps.build.outputs.digest }} steps: @@ -188,6 +191,7 @@ jobs: 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' @@ -203,6 +207,7 @@ jobs: 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' @@ -217,6 +222,7 @@ jobs: name: Complete deployment and notify 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' }} 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/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 83b219cf8..f4a7738e4 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,21 +48,21 @@ "@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", "@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", @@ -75,19 +73,19 @@ "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.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", - "swagger-ui-react": "^5.30.3", + "swagger-ui-react": "^5.31.0", "use-deep-compare-effect": "^1.8.1", "zod": "^4.1.13" }, @@ -96,13 +94,13 @@ "@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", "@types/swagger-ui-react": "^5.18.0", "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 090c98056..abeaf6f8d 100644 --- a/package.json +++ b/package.json @@ -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 9bf59dcbb..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.10", - "react": "19.2.1", - "react-dom": "19.2.1", + "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 77e2c963a..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.14", + "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 cd636ced4..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.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 7ce4d561e..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"; 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/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 8cc811cac..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.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", - "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 91623e279..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.10", - "react": "19.2.1", - "react-dom": "19.2.1", + "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 ea7a55ece..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.10", - "react": "19.2.1", - "react-dom": "19.2.1", + "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 cb1cda699..dd5249a24 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; @@ -102,14 +106,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 cb9e766dd..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", + "@mantine/dates": "^8.3.10", "next": "16.0.10", - "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/spotlight/package.json b/packages/spotlight/package.json index 89885f642..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", + "jotai": "^2.16.0", "next": "16.0.10", - "react": "19.2.1", - "react-dom": "19.2.1", + "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 dee4f50b8..86a78810e 100644 --- a/packages/translation/package.json +++ b/packages/translation/package.json @@ -33,15 +33,15 @@ "deepmerge": "4.3.1", "mantine-react-table": "2.0.0-beta.9", "next": "16.0.10", - "next-intl": "4.5.8", - "react": "19.2.1", - "react-dom": "19.2.1" + "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/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 cb62e1008..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.10", - "react": "19.2.1", - "react-dom": "19.2.1", + "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 0a33794f3..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,9 +48,9 @@ "@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": "3.13.0", "@tiptap/extension-highlight": "3.13.0", @@ -73,8 +73,8 @@ "mantine-form-zod-resolver": "^1.3.0", "mantine-react-table": "2.0.0-beta.9", "next": "16.0.10", - "react": "19.2.1", - "react-dom": "19.2.1", + "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/downloads/component.tsx b/packages/widgets/src/downloads/component.tsx index 02e58efd2..9af29da6d 100644 --- a/packages/widgets/src/downloads/component.tsx +++ b/packages/widgets/src/downloads/component.tsx @@ -473,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)} 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/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 474d17d3c..502582e78 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.5.1)) + 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.5.1)) '@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.5.1)) + 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.5.1)) 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.5.1) + 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.5.1) 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,50 +209,50 @@ 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@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.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) '@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.10(@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.10(@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) @@ -290,17 +284,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.10 - version: 16.0.10(@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) + 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) @@ -308,29 +302,29 @@ 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 swagger-ui-react: - specifier: ^5.30.3 - version: 5.30.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + specifier: ^5.31.0 + version: 5.31.0(@types/react@19.2.7)(react-dom@19.2.3(react@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) 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 @@ -366,8 +360,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)) @@ -389,6 +383,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 @@ -410,9 +407,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 @@ -457,8 +451,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 @@ -466,8 +460,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 @@ -489,15 +483,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 @@ -530,8 +524,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 @@ -541,12 +535,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 @@ -567,8 +561,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 @@ -578,9 +572,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 @@ -611,9 +602,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 @@ -643,37 +631,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.10 - version: 16.0.10(@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) + 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 @@ -688,8 +676,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 @@ -705,9 +693,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 @@ -720,9 +705,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 @@ -733,20 +715,20 @@ importers: specifier: ^0.9.1 version: 0.9.1 ldapts: - specifier: 8.0.14 - version: 8.0.14 + specifier: 8.0.23 + version: 8.0.23 next: specifier: 16.0.10 - version: 16.0.10(@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) + 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.10(@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 @@ -767,8 +749,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 @@ -782,11 +764,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 @@ -798,36 +780,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 @@ -866,8 +820,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 @@ -877,30 +831,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.10 - version: 16.0.10(@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) + 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 @@ -921,8 +869,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 @@ -932,9 +880,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 @@ -948,9 +917,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 @@ -966,27 +941,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 @@ -1007,8 +979,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 @@ -1029,8 +1001,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 @@ -1046,6 +1018,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 @@ -1064,9 +1039,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 @@ -1099,8 +1071,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 @@ -1110,6 +1082,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 @@ -1130,8 +1105,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 @@ -1150,24 +1125,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 @@ -1178,11 +1150,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 @@ -1206,8 +1178,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 @@ -1215,8 +1187,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 @@ -1233,8 +1205,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 @@ -1249,8 +1221,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 @@ -1283,8 +1255,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 @@ -1301,11 +1273,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 @@ -1320,8 +1292,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 @@ -1353,11 +1325,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 @@ -1372,8 +1344,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 @@ -1383,12 +1355,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 @@ -1400,23 +1372,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 @@ -1437,8 +1406,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 @@ -1457,12 +1426,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 @@ -1472,9 +1441,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) @@ -1537,39 +1503,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.19.0 - version: 3.19.0 - 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 @@ -1583,14 +1518,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 @@ -1602,8 +1537,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 @@ -1644,23 +1579,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.10 - version: 16.0.10(@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) + 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 @@ -1675,8 +1610,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 @@ -1687,11 +1622,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 @@ -1703,8 +1638,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 @@ -1714,6 +1649,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 @@ -1723,9 +1661,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 @@ -1745,23 +1680,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.10 - version: 16.0.10(@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) + 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 @@ -1785,8 +1720,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 @@ -1810,23 +1745,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 @@ -1838,8 +1770,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 @@ -1858,9 +1790,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 @@ -1878,8 +1807,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 @@ -1892,6 +1821,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 @@ -1904,9 +1836,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 @@ -1936,8 +1865,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 @@ -1961,8 +1890,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 @@ -1979,17 +1908,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.10 - version: 16.0.10(@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) + 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 @@ -2001,8 +1930,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 @@ -2040,32 +1969,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.10 - version: 16.0.10(@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) + 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 @@ -2077,8 +2006,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 @@ -2099,19 +2028,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.10 - version: 16.0.10(@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) + 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.10(@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 @@ -2123,8 +2052,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 @@ -2137,9 +2066,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 @@ -2147,29 +2073,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.10 - version: 16.0.10(@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) + 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 @@ -2187,8 +2113,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 @@ -2218,8 +2144,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 @@ -2228,10 +2154,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 @@ -2244,6 +2170,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 @@ -2262,9 +2191,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 @@ -2296,17 +2222,17 @@ 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: 3.13.0 version: 3.13.0(@tiptap/extension-text-style@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))) @@ -2351,7 +2277,7 @@ importers: version: 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0)) '@tiptap/react': 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.1(react@19.2.1))(react@19.2.1) + 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: ^3.13.0 version: 3.13.0 @@ -2363,25 +2289,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.10 - version: 16.0.10(@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) + 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 @@ -2402,8 +2328,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 @@ -2415,25 +2341,25 @@ importers: 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 @@ -2442,8 +2368,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 @@ -2473,20 +2399,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==} @@ -2502,11 +2428,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==} @@ -3223,8 +3149,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': @@ -3332,7 +3258,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==} @@ -3604,88 +3530,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 @@ -4043,8 +3969,8 @@ packages: '@remirror/core-constants@3.0.0': resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==} - '@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==} @@ -4222,8 +4148,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' @@ -4521,14 +4447,14 @@ packages: '@tanstack/virtual-core@3.11.2': resolution: {integrity: sha512-vTtpNt7mKCiZ1pwU9hfKPhpdVO2sVzFQsxoVBGtOSHxlrRRzYr8iQ2TlwbAcRYCcEiZ9ECAM8kBzH0v2+VzfKw==} - '@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@3.13.0': resolution: {integrity: sha512-iUelgiTMgPVMpY5ZqASUpk8mC8HuR9FWKaDzK27w9oWip9tuB54Z8mePTxNcQaSPb6ErzEaC8x8egrRt7OsdGQ==} @@ -4751,19 +4677,19 @@ packages: tree-sitter: optional: true - '@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' @@ -4774,27 +4700,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' @@ -4984,14 +4910,14 @@ packages: '@types/node@18.19.50': resolution: {integrity: sha512-xonK+NRrMBRtkL1hVCc3G+uXtjh1Al4opBLjqVmipe5ZAaBYWW6cNAiBVZ1BvmkBhep698rP3UM3aRAdSALuhg==} - '@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==} @@ -5067,63 +4993,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': @@ -5149,11 +5075,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==} @@ -6067,8 +5993,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: @@ -6307,8 +6233,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==} @@ -6335,8 +6261,8 @@ packages: dompurify@3.2.6: resolution: {integrity: sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==} - 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==} @@ -6373,8 +6299,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' @@ -6748,8 +6674,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: @@ -6890,8 +6816,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: @@ -7228,9 +7154,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==} @@ -7308,8 +7231,8 @@ packages: highlightjs-vue@1.0.0: resolution: {integrity: sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==} - 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: @@ -7779,8 +7702,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: @@ -7829,8 +7752,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' @@ -7870,8 +7793,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 @@ -7964,8 +7887,8 @@ packages: resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} engines: {node: '>= 0.6.3'} - ldapts@8.0.14: - resolution: {integrity: sha512-XWYc1Ncw2zK22mq9Nmy6MzMFKPW73EJcUpSmKhi3PNsCPIQp3jkL3Q+yNoGGVzBYqyxMWDIM8fuw9t4ttNe6iw==} + ldapts@8.0.23: + resolution: {integrity: sha512-HjTGtXlSEAMniCI4z9nrf5YWLgq4Mc0XJJYliiLY79/IU8lgNX5gEMjzRGncpBJmVJooxI6xFSKzFbs1KuHL7w==} engines: {node: '>=20'} levn@0.4.1: @@ -8081,14 +8004,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==} @@ -8439,11 +8362,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 @@ -9019,8 +8942,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==} @@ -9297,10 +9220,10 @@ packages: peerDependencies: react: ^15.3.0 || 16 || 17 || 18 - 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==} @@ -9330,8 +9253,8 @@ packages: peerDependencies: react: ^16.8.4 || ^17.0.0 || ^18.0.0 - 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==} @@ -9425,8 +9348,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: @@ -9693,8 +9616,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 @@ -10151,8 +10074,8 @@ packages: swagger-client@3.36.0: resolution: {integrity: sha512-9fkjxGHXuKy20jj8zwE6RwgFSOGKAyOD5U7aKgW/+/futtHZHOdZeqiEkb97sptk2rdBv7FEiUQDNlWZR186RA==} - swagger-ui-react@5.30.3: - resolution: {integrity: sha512-QIy32nPql6yiV2NVwbww1P7f6HEOAuYrnk8VEJkzPC/p6Xc5Xnz9hhmSHzXTuM70fDbVw/qPzCJ0mZMMultpiw==} + swagger-ui-react@5.31.0: + resolution: {integrity: sha512-E/sTgKADThzpVksaGXbhED0pQCYdajiBNOzvSAan+RhV7pdoi2qvdwWhZsIo8nRvHk9UXJ0nkuxrud854ICr7A==} peerDependencies: react: '>=16.8.0 <20' react-dom: '>=16.8.0 <20' @@ -10220,8 +10143,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==} @@ -10556,8 +10479,8 @@ packages: types-ramda@0.30.1: resolution: {integrity: sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==} - 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 @@ -10736,8 +10659,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 @@ -10826,7 +10749,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 @@ -11210,23 +11133,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: @@ -11243,21 +11166,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': {} @@ -11386,7 +11309,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 @@ -11410,14 +11333,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 @@ -11696,36 +11619,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': {} @@ -11905,9 +11828,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': {} @@ -11936,13 +11859,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': {} @@ -11955,7 +11878,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 @@ -11994,18 +11917,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': {} @@ -12082,9 +12005,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': {} @@ -12293,7 +12216,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 @@ -12326,95 +12249,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@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.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) + '@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.1(react@19.2.1))(react@19.2.1) - react: 19.2.1 - react-dom: 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) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) '@million/install@1.0.14': dependencies: @@ -12436,7 +12359,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 @@ -12444,7 +12367,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 @@ -12736,7 +12659,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 @@ -12787,7 +12709,7 @@ snapshots: '@remirror/core-constants@3.0.0': {} - '@rolldown/pluginutils@1.0.0-beta.47': {} + '@rolldown/pluginutils@1.0.0-beta.53': {} '@rollup/pluginutils@5.1.0': dependencies: @@ -12935,9 +12857,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 @@ -13412,10 +13334,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': {} @@ -13427,56 +13349,56 @@ 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.10(@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.10(@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': {} '@tanstack/virtual-core@3.11.2': {} - '@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 @@ -13663,7 +13585,7 @@ snapshots: prosemirror-transform: 1.10.2 prosemirror-view: 1.41.4 - '@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.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': 3.13.0(@tiptap/pm@3.13.0) '@tiptap/pm': 3.13.0 @@ -13671,9 +13593,9 @@ snapshots: '@types/react-dom': 19.2.3(@types/react@19.2.7) '@types/use-sync-external-store': 0.0.6 fast-equals: 5.3.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) + 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) @@ -13717,43 +13639,43 @@ snapshots: tree-sitter: 0.22.4 optional: true - '@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.10(@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.10(@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': {} @@ -13766,7 +13688,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 @@ -13776,7 +13698,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: @@ -13802,11 +13724,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': {} @@ -13833,16 +13755,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: @@ -13852,7 +13774,7 @@ snapshots: '@types/connect@3.4.38': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.4 '@types/cookie@0.4.1': {} @@ -13861,11 +13783,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': {} @@ -13903,13 +13825,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': @@ -13920,7 +13842,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 @@ -13935,7 +13857,7 @@ snapshots: '@types/glob@7.2.0': dependencies: '@types/minimatch': 5.1.2 - '@types/node': 24.10.1 + '@types/node': 24.10.4 '@types/hast@3.0.4': dependencies: @@ -13979,7 +13901,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)': @@ -13990,15 +13912,15 @@ snapshots: dependencies: undici-types: 5.26.5 - '@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 @@ -14025,21 +13947,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': @@ -14048,7 +13970,7 @@ snapshots: '@types/stream-buffers@3.0.7': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.4 '@types/swagger-ui-react@5.18.0': dependencies: @@ -14056,7 +13978,7 @@ snapshots: '@types/through@0.0.33': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.4 '@types/tinycolor2@1.4.6': {} @@ -14075,22 +13997,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) @@ -14098,56 +14019,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 @@ -14157,20 +14078,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': {} @@ -14205,15 +14126,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.5.1))': + '@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.5.1))': 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.5.1) + 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.5.1) transitivePeerDependencies: - supports-color @@ -14230,7 +14151,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.5.1) + 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.5.1) transitivePeerDependencies: - supports-color @@ -14243,13 +14164,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.5.1))': + '@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.5.1))': 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.5.1) + 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.5.1) '@vitest/pretty-format@4.0.15': dependencies: @@ -14277,7 +14198,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.5.1) + 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.5.1) '@vitest/utils@4.0.15': dependencies: @@ -15184,7 +15105,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 @@ -15242,9 +15163,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: @@ -15429,8 +15350,7 @@ snapshots: detect-indent@7.0.1: {} - detect-libc@1.0.3: - optional: true + detect-libc@1.0.3: {} detect-libc@2.1.2: {} @@ -15450,9 +15370,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: @@ -15494,7 +15414,7 @@ snapshots: optionalDependencies: '@types/trusted-types': 2.0.7 - dompurify@3.3.0: + dompurify@3.3.1: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -15534,19 +15454,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: @@ -15606,7 +15526,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 @@ -15955,14 +15875,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: @@ -15973,17 +15893,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 @@ -15992,9 +15912,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 @@ -16006,13 +15926,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 @@ -16022,7 +15942,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 @@ -16031,17 +15951,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 @@ -16049,7 +15969,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 @@ -16063,10 +15983,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: @@ -16083,15 +16003,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 @@ -16266,7 +16186,7 @@ snapshots: fast-uri@3.0.6: {} - fast-xml-parser@5.3.2: + fast-xml-parser@5.3.3: dependencies: strnum: 2.1.0 @@ -16649,8 +16569,6 @@ snapshots: chalk: 4.1.2 tinygradient: 1.1.5 - graphemer@1.4.0: {} - h3@1.15.1: dependencies: cookie-es: 1.2.2 @@ -16753,7 +16671,7 @@ snapshots: highlightjs-vue@1.0.0: {} - hono@4.10.7: {} + hono@4.11.1: {} hook-std@4.0.0: {} @@ -16763,7 +16681,7 @@ snapshots: hosted-git-info@9.0.2: dependencies: - lru-cache: 11.2.2 + lru-cache: 11.2.4 hpagent@1.2.0: {} @@ -17202,10 +17120,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 @@ -17272,25 +17190,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: optional: true @@ -17311,11 +17229,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 @@ -17412,7 +17330,7 @@ snapshots: dependencies: readable-stream: 2.3.8 - ldapts@8.0.14: + ldapts@8.0.23: dependencies: '@types/asn1': 0.2.4 asn1: 0.2.6 @@ -17532,10 +17450,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 @@ -17570,24 +17488,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: {} @@ -17975,38 +17893,39 @@ snapshots: netmask@2.0.2: {} - 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.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.10(@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.10(@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.10(@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.10(@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.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.10 '@next/swc-darwin-x64': 16.0.10 @@ -18017,21 +17936,21 @@ snapshots: '@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.10(@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.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.10 '@next/swc-darwin-x64': 16.0.10 @@ -18042,7 +17961,7 @@ snapshots: '@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' @@ -18067,8 +17986,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: {} @@ -18568,7 +18486,7 @@ snapshots: optionalDependencies: fsevents: 2.3.2 - po-parser@1.0.2: {} + po-parser@2.0.0: {} possible-typed-array-names@1.0.0: {} @@ -18683,7 +18601,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: @@ -18814,7 +18732,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: @@ -18894,53 +18812,53 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 - react-copy-to-clipboard@5.1.0(react@19.2.1): + react-copy-to-clipboard@5.1.0(react@19.2.3): dependencies: copy-to-clipboard: 3.3.3 prop-types: 15.8.1 - react: 19.2.1 + react: 19.2.3 - react-debounce-input@3.3.0(react@19.2.1): + react-debounce-input@3.3.0(react@19.2.3): dependencies: lodash.debounce: 4.0.8 prop-types: 15.8.1 - react: 19.2.1 + react: 19.2.3 - 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-immutable-proptypes@2.2.0(immutable@3.8.2): dependencies: immutable: 3.8.2 invariant: 2.2.4 - react-immutable-pure-component@2.2.2(immutable@3.8.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + react-immutable-pure-component@2.2.2(immutable@3.8.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: immutable: 3.8.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) - react-inspector@6.0.2(react@19.2.1): + react-inspector@6.0.2(react@19.2.3): dependencies: - 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 @@ -18949,7 +18867,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 @@ -18958,38 +18876,38 @@ 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-redux@9.2.0(@types/react@19.2.7)(react@19.2.1)(redux@5.0.1): + react-redux@9.2.0(@types/react@19.2.7)(react@19.2.3)(redux@5.0.1): dependencies: '@types/use-sync-external-store': 0.0.6 - react: 19.2.1 - use-sync-external-store: 1.4.0(react@19.2.1) + react: 19.2.3 + use-sync-external-store: 1.4.0(react@19.2.3) optionalDependencies: '@types/react': 19.2.7 redux: 5.0.1 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 @@ -19001,56 +18919,56 @@ 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-syntax-highlighter@16.1.0(react@19.2.1): + react-syntax-highlighter@16.1.0(react@19.2.3): dependencies: '@babel/runtime': 7.28.4 highlight.js: 10.7.3 highlightjs-vue: 1.0.0 lowlight: 1.20.0 prismjs: 1.30.0 - react: 19.2.1 + react: 19.2.3 refractor: 5.0.0 - 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: @@ -19124,15 +19042,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 @@ -19398,7 +19316,7 @@ snapshots: safer-buffer@2.1.2: {} - sass@1.94.2: + sass@1.96.0: dependencies: chokidar: 4.0.0 immutable: 5.0.2 @@ -19434,7 +19352,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) @@ -19916,17 +19834,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 @@ -19992,7 +19910,7 @@ snapshots: transitivePeerDependencies: - debug - swagger-ui-react@5.30.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + swagger-ui-react@5.31.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: '@babel/runtime-corejs3': 7.27.1 '@scarf/scarf': 1.4.0 @@ -20010,15 +19928,15 @@ snapshots: prop-types: 15.8.1 randexp: 0.5.3 randombytes: 2.1.0 - react: 19.2.1 - react-copy-to-clipboard: 5.1.0(react@19.2.1) - react-debounce-input: 3.3.0(react@19.2.1) - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-copy-to-clipboard: 5.1.0(react@19.2.3) + react-debounce-input: 3.3.0(react@19.2.3) + react-dom: 19.2.3(react@19.2.3) react-immutable-proptypes: 2.2.0(immutable@3.8.2) - react-immutable-pure-component: 2.2.2(immutable@3.8.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - react-inspector: 6.0.2(react@19.2.1) - react-redux: 9.2.0(@types/react@19.2.7)(react@19.2.1)(redux@5.0.1) - react-syntax-highlighter: 16.1.0(react@19.2.1) + react-immutable-pure-component: 2.2.2(immutable@3.8.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react-inspector: 6.0.2(react@19.2.3) + react-redux: 9.2.0(@types/react@19.2.7)(react@19.2.3)(redux@5.0.1) + react-syntax-highlighter: 16.1.0(react@19.2.3) redux: 5.0.1 redux-immutable: 4.0.0(immutable@3.8.2) remarkable: 2.0.1 @@ -20109,7 +20027,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 @@ -20265,9 +20183,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 @@ -20282,14 +20200,14 @@ snapshots: ts-mixer@6.0.4: {} - 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 @@ -20481,13 +20399,13 @@ snapshots: dependencies: ts-toolbelt: 9.6.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 @@ -20656,54 +20574,54 @@ 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: {} @@ -20775,18 +20693,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.5.1)): + 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.5.1)): 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.5.1) + 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.5.1) 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.5.1): + 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.5.1): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -20795,18 +20713,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.5.1 - 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.5.1): + 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.5.1): 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.5.1)) + '@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.5.1)) '@vitest/pretty-format': 4.0.15 '@vitest/runner': 4.0.15 '@vitest/snapshot': 4.0.15 @@ -20823,12 +20741,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.5.1) + 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.5.1) 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 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/tooling/eslint/package.json b/tooling/eslint/package.json index 6491b4f55..f5deebad2 100644 --- a/tooling/eslint/package.json +++ b/tooling/eslint/package.json @@ -24,12 +24,12 @@ "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,