From 44bf36fff93a016a456e3114fe26daed55400438 Mon Sep 17 00:00:00 2001 From: "homarr-renovate[bot]" <158783068+homarr-renovate[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 00:01:35 +0000 Subject: [PATCH 01/16] chore(deps): update dependency timezones-ical-library to ^2.2.0 (#5640) Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 12 ++++++------ pnpm-workspace.yaml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 36318302a..62bcb326a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -505,8 +505,8 @@ catalogs: specifier: ^11.14.0 version: 11.14.0 timezones-ical-library: - specifier: ^2.1.3 - version: 2.1.3 + specifier: ^2.2.0 + version: 2.2.0 trpc-to-openapi: specifier: ^3.2.0 version: 3.2.0 @@ -2043,7 +2043,7 @@ importers: version: 1.1.1 timezones-ical-library: specifier: 'catalog:' - version: 2.1.3 + version: 2.2.0 tsdav: specifier: 'catalog:' version: 2.2.0 @@ -10351,8 +10351,8 @@ packages: resolution: {integrity: sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==} engines: {node: '>=12'} - timezones-ical-library@2.1.3: - resolution: {integrity: sha512-3ihogrr88TefPpa2og2fHw22VsWxlPUN4YDiLZRV9pbRsmguMyDsHhm8i0kkj7ihQYFgERZkj6eVOYY+TSOaOw==} + timezones-ical-library@2.2.0: + resolution: {integrity: sha512-D8pKEBzYnCAGZSEyinMZqh2yTc7ZJqonXtNkpTzhPuJlkJjPEUJOXBs7ExeHcHGBaGpaJRTye5t0mLR20BeklA==} engines: {node: '>=22.18.0', npm: '>=10.9.3'} tiny-invariant@1.3.3: @@ -19328,7 +19328,7 @@ snapshots: dependencies: convert-hrtime: 5.0.0 - timezones-ical-library@2.1.3: {} + timezones-ical-library@2.2.0: {} tiny-invariant@1.3.3: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index f083b5141..e2229309f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -169,7 +169,7 @@ catalog: svgson: ^5.3.1 swagger-ui-react: ^5.32.5 testcontainers: ^11.14.0 - timezones-ical-library: ^2.1.3 + timezones-ical-library: ^2.2.0 trpc-to-openapi: ^3.2.0 tsdav: ^2.2.0 tsx: 4.20.4 From 2829d145afb052586843f337a85a37410682af70 Mon Sep 17 00:00:00 2001 From: "homarr-renovate[bot]" <158783068+homarr-renovate[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 02:56:52 +0000 Subject: [PATCH 02/16] chore(deps): update dependency zod to ^4.4.1 (#5641) Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 93 ++++++++++++++++++++++++--------------------- pnpm-workspace.yaml | 2 +- 2 files changed, 50 insertions(+), 45 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 62bcb326a..d5780c116 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -553,8 +553,8 @@ catalogs: specifier: 2.8.3 version: 2.8.3 zod: - specifier: ^4.3.6 - version: 4.3.6 + specifier: ^4.4.1 + version: 4.4.2 zod-form-data: specifier: ^3.0.1 version: 3.0.1 @@ -888,7 +888,7 @@ importers: version: 2.8.3 zod: specifier: 'catalog:' - version: 4.3.6 + version: 4.4.2 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -1234,10 +1234,10 @@ importers: version: 2.2.6 trpc-to-openapi: specifier: 'catalog:' - version: 3.2.0(patch_hash=2ca3c16af0fcca0c736697ad4fe553a14f794524fa9ce0d5c3e8ee4aea76090c)(@trpc/server@11.17.0(typescript@5.9.3))(zod-openapi@5.3.0(zod@4.3.6))(zod@4.3.6) + version: 3.2.0(patch_hash=2ca3c16af0fcca0c736697ad4fe553a14f794524fa9ce0d5c3e8ee4aea76090c)(@trpc/server@11.17.0(typescript@5.9.3))(zod-openapi@5.3.0(zod@4.4.2))(zod@4.4.2) zod: specifier: 'catalog:' - version: 4.3.6 + version: 4.4.2 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -1304,7 +1304,7 @@ importers: version: 19.2.5(react@19.2.5) zod: specifier: 'catalog:' - version: 4.3.6 + version: 4.4.2 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -1430,10 +1430,10 @@ importers: version: 7.24.6 zod: specifier: 'catalog:' - version: 4.3.6 + version: 4.4.2 zod-validation-error: specifier: 'catalog:' - version: 5.0.0(zod@4.3.6) + version: 5.0.0(zod@4.4.2) devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -1455,7 +1455,7 @@ importers: dependencies: '@t3-oss/env-nextjs': specifier: 'catalog:' - version: 0.13.11(arktype@2.1.20)(typescript@5.9.3)(zod@4.3.6) + version: 0.13.11(arktype@2.1.20)(typescript@5.9.3)(zod@4.4.2) better-sqlite3: specifier: 'catalog:' version: 12.9.0 @@ -1482,7 +1482,7 @@ importers: version: 3.19.0 zod: specifier: 'catalog:' - version: 4.3.6 + version: 4.4.2 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -1534,7 +1534,7 @@ importers: version: 19.2.5 zod: specifier: 'catalog:' - version: 4.3.6 + version: 4.4.2 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -1721,7 +1721,7 @@ importers: version: 0.45.2(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.9.0)(gel@2.0.0)(mysql2@3.22.3(@types/node@24.12.2))(pg@8.20.0) drizzle-zod: specifier: 'catalog:' - version: 0.8.3(drizzle-orm@0.45.2(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.9.0)(gel@2.0.0)(mysql2@3.22.3(@types/node@24.12.2))(pg@8.20.0))(zod@4.3.6) + version: 0.8.3(drizzle-orm@0.45.2(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.9.0)(gel@2.0.0)(mysql2@3.22.3(@types/node@24.12.2))(pg@8.20.0))(zod@4.4.2) mysql2: specifier: 'catalog:' version: 3.22.3(@types/node@24.12.2) @@ -1776,7 +1776,7 @@ importers: version: 5.7.2 zod: specifier: 'catalog:' - version: 4.3.6 + version: 4.4.2 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -1844,10 +1844,10 @@ importers: version: 8.3.18(react@19.2.5) mantine-form-zod-resolver: specifier: 'catalog:' - version: 1.3.0(@mantine/form@8.3.18(react@19.2.5))(zod@4.3.6) + version: 1.3.0(@mantine/form@8.3.18(react@19.2.5))(zod@4.4.2) zod: specifier: 'catalog:' - version: 4.3.6 + version: 4.4.2 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -1899,7 +1899,7 @@ importers: version: 19.2.5 zod: specifier: 'catalog:' - version: 4.3.6 + version: 4.4.2 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -2055,7 +2055,7 @@ importers: version: 0.6.2 zod: specifier: 'catalog:' - version: 4.3.6 + version: 4.4.2 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -2168,7 +2168,7 @@ importers: version: 19.2.5(react@19.2.5) zod: specifier: 'catalog:' - version: 4.3.6 + version: 4.4.2 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -2272,10 +2272,10 @@ importers: version: 2.2.6 zod: specifier: 'catalog:' - version: 4.3.6 + version: 4.4.2 zod-form-data: specifier: 'catalog:' - version: 3.0.1(zod@4.3.6) + version: 3.0.1(zod@4.4.2) devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -2303,7 +2303,7 @@ importers: version: link:../common zod: specifier: 'catalog:' - version: 4.3.6 + version: 4.4.2 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -2423,7 +2423,7 @@ importers: version: 7.24.6 zod: specifier: 'catalog:' - version: 4.3.6 + version: 4.4.2 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -2705,10 +2705,10 @@ importers: version: link:../translation zod: specifier: 'catalog:' - version: 4.3.6 + version: 4.4.2 zod-form-data: specifier: 'catalog:' - version: 3.0.1(zod@4.3.6) + version: 3.0.1(zod@4.4.2) devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -2865,7 +2865,7 @@ importers: version: 1.11.20 mantine-form-zod-resolver: specifier: 'catalog:' - version: 1.3.0(@mantine/form@8.3.18(react@19.2.5))(zod@4.3.6) + version: 1.3.0(@mantine/form@8.3.18(react@19.2.5))(zod@4.4.2) mantine-react-table: specifier: 'catalog:' version: 2.0.0-beta.9(@mantine/core@8.3.18(@mantine/hooks@8.3.18(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@mantine/dates@8.3.18(@mantine/core@8.3.18(@mantine/hooks@8.3.18(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@mantine/hooks@8.3.18(react@19.2.5))(dayjs@1.11.20)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@mantine/hooks@8.3.18(react@19.2.5))(@tabler/icons-react@3.41.1(react@19.2.5))(clsx@2.1.1)(dayjs@1.11.20)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) @@ -2892,7 +2892,7 @@ importers: version: 8.23.7 zod: specifier: 'catalog:' - version: 4.3.6 + version: 4.4.2 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -11185,6 +11185,9 @@ packages: zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + zod@4.4.2: + resolution: {integrity: sha512-IynmDyxsEsb9RKzO3J9+4SxXnl2FTFSzNBaKKaMV6tsSk0rw9gYw9gs+JFCq/qk2LCZ78KDwyj+Z289TijSkUw==} + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -13473,19 +13476,19 @@ snapshots: dependencies: '@swc/counter': 0.1.3 - '@t3-oss/env-core@0.13.11(arktype@2.1.20)(typescript@5.9.3)(zod@4.3.6)': + '@t3-oss/env-core@0.13.11(arktype@2.1.20)(typescript@5.9.3)(zod@4.4.2)': optionalDependencies: arktype: 2.1.20 typescript: 5.9.3 - zod: 4.3.6 + zod: 4.4.2 - '@t3-oss/env-nextjs@0.13.11(arktype@2.1.20)(typescript@5.9.3)(zod@4.3.6)': + '@t3-oss/env-nextjs@0.13.11(arktype@2.1.20)(typescript@5.9.3)(zod@4.4.2)': dependencies: - '@t3-oss/env-core': 0.13.11(arktype@2.1.20)(typescript@5.9.3)(zod@4.3.6) + '@t3-oss/env-core': 0.13.11(arktype@2.1.20)(typescript@5.9.3)(zod@4.4.2) optionalDependencies: arktype: 2.1.20 typescript: 5.9.3 - zod: 4.3.6 + zod: 4.4.2 '@tabler/icons-react@3.41.1(react@19.2.5)': dependencies: @@ -15365,10 +15368,10 @@ snapshots: mysql2: 3.22.3(@types/node@24.12.2) pg: 8.20.0 - drizzle-zod@0.8.3(drizzle-orm@0.45.2(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.9.0)(gel@2.0.0)(mysql2@3.22.3(@types/node@24.12.2))(pg@8.20.0))(zod@4.3.6): + drizzle-zod@0.8.3(drizzle-orm@0.45.2(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.9.0)(gel@2.0.0)(mysql2@3.22.3(@types/node@24.12.2))(pg@8.20.0))(zod@4.4.2): dependencies: drizzle-orm: 0.45.2(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.9.0)(gel@2.0.0)(mysql2@3.22.3(@types/node@24.12.2))(pg@8.20.0) - zod: 4.3.6 + zod: 4.4.2 dunder-proto@1.0.1: dependencies: @@ -16996,10 +16999,10 @@ snapshots: dependencies: semver: 7.7.4 - mantine-form-zod-resolver@1.3.0(@mantine/form@8.3.18(react@19.2.5))(zod@4.3.6): + mantine-form-zod-resolver@1.3.0(@mantine/form@8.3.18(react@19.2.5))(zod@4.4.2): dependencies: '@mantine/form': 8.3.18(react@19.2.5) - zod: 4.3.6 + zod: 4.4.2 mantine-react-table@2.0.0-beta.9(@mantine/core@8.3.18(@mantine/hooks@8.3.18(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@mantine/dates@8.3.18(@mantine/core@8.3.18(@mantine/hooks@8.3.18(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@mantine/hooks@8.3.18(react@19.2.5))(dayjs@1.11.20)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@mantine/hooks@8.3.18(react@19.2.5))(@tabler/icons-react@3.41.1(react@19.2.5))(clsx@2.1.1)(dayjs@1.11.20)(react-dom@19.2.5(react@19.2.5))(react@19.2.5): dependencies: @@ -19419,14 +19422,14 @@ snapshots: trough@2.2.0: {} - trpc-to-openapi@3.2.0(patch_hash=2ca3c16af0fcca0c736697ad4fe553a14f794524fa9ce0d5c3e8ee4aea76090c)(@trpc/server@11.17.0(typescript@5.9.3))(zod-openapi@5.3.0(zod@4.3.6))(zod@4.3.6): + trpc-to-openapi@3.2.0(patch_hash=2ca3c16af0fcca0c736697ad4fe553a14f794524fa9ce0d5c3e8ee4aea76090c)(@trpc/server@11.17.0(typescript@5.9.3))(zod-openapi@5.3.0(zod@4.4.2))(zod@4.4.2): dependencies: '@trpc/server': 11.17.0(typescript@5.9.3) co-body: 6.2.0 h3: 1.15.1 openapi3-ts: 4.4.0 - zod: 4.3.6 - zod-openapi: 5.3.0(zod@4.3.6) + zod: 4.4.2 + zod-openapi: 5.3.0(zod@4.4.2) optionalDependencies: '@rollup/rollup-linux-x64-gnu': 4.6.1 @@ -20156,23 +20159,25 @@ snapshots: compress-commons: 6.0.2 readable-stream: 4.5.2 - zod-form-data@3.0.1(zod@4.3.6): + zod-form-data@3.0.1(zod@4.4.2): dependencies: '@rvf/set-get': 7.0.1 - zod: 4.3.6 + zod: 4.4.2 - zod-openapi@5.3.0(zod@4.3.6): + zod-openapi@5.3.0(zod@4.4.2): dependencies: - zod: 4.3.6 + zod: 4.4.2 zod-validation-error@4.0.2(zod@4.3.6): dependencies: zod: 4.3.6 - zod-validation-error@5.0.0(zod@4.3.6): + zod-validation-error@5.0.0(zod@4.4.2): dependencies: - zod: 4.3.6 + zod: 4.4.2 zod@4.3.6: {} + zod@4.4.2: {} + zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index e2229309f..92b579459 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -185,7 +185,7 @@ catalog: ws: ^8.20.0 xml2js: ^0.6.2 yaml: 2.8.3 - zod: ^4.3.6 + zod: ^4.4.1 zod-form-data: ^3.0.1 zod-validation-error: ^5.0.0 catalogMode: strict From 7c83d2cb6e996970d93fe5a369dbb328842d2137 Mon Sep 17 00:00:00 2001 From: "homarr-renovate[bot]" <158783068+homarr-renovate[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 10:54:29 +0000 Subject: [PATCH 03/16] chore(deps): update dependency hono@<4.6.5 to >=4.12.16 (#5642) Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index f1be117ec..0b91e4a5e 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "brace-expansion@>=1.0.0 <=1.1.11": ">=5.0.5", "esbuild@<=0.24.2": ">=0.28.0", "form-data@>=4.0.0 <4.0.4": ">=4.0.5", - "hono@<4.6.5": ">=4.12.15", + "hono@<4.6.5": ">=4.12.16", "linkifyjs@<4.3.2": ">=4.3.2", "nanoid@>=4.0.0 <5.0.9": ">=5.1.9", "prismjs@<1.30.0": ">=1.30.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d5780c116..deb1c5079 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -570,7 +570,7 @@ overrides: brace-expansion@>=1.0.0 <=1.1.11: '>=5.0.5' esbuild@<=0.24.2: '>=0.28.0' form-data@>=4.0.0 <4.0.4: '>=4.0.5' - hono@<4.6.5: '>=4.12.15' + hono@<4.6.5: '>=4.12.16' linkifyjs@<4.3.2: '>=4.3.2' nanoid@>=4.0.0 <5.0.9: '>=5.1.9' prismjs@<1.30.0: '>=1.30.0' @@ -3897,7 +3897,7 @@ packages: resolution: {integrity: sha512-kz323qIQkNQElEGroo/E9MKPDuIR5pkuk/XEWd50K+cSEKdmdiYx0PKWUdaNY2ecJYngtF+njDMsMKplL6zfEg==} engines: {node: '>=18.14.1'} peerDependencies: - hono: '>=4.12.15' + hono: '>=4.12.16' '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} @@ -7739,8 +7739,8 @@ packages: highlightjs-vue@1.0.0: resolution: {integrity: sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==} - hono@4.12.15: - resolution: {integrity: sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==} + hono@4.12.16: + resolution: {integrity: sha512-jN0ZewiNAWSe5khM3EyCmBb250+b40wWbwNILNfEvq84VREWwOIkuUsFONk/3i3nqkz7Oe1PcpM2mwQEK2L9Kg==} engines: {node: '>=16.9.0'} hook-std@4.0.0: @@ -11981,9 +11981,9 @@ snapshots: - undici - utf-8-validate - '@hono/node-server@1.13.0(hono@4.12.15)': + '@hono/node-server@1.13.0(hono@4.12.16)': dependencies: - hono: 4.12.15 + hono: 4.12.16 '@humanfs/core@0.19.1': {} @@ -12438,7 +12438,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.12.15) + '@hono/node-server': 1.13.0(hono@4.12.16) '@million/install': 1.0.14 '@rollup/pluginutils': 5.1.0 '@rrweb/types': 2.0.0-alpha.16 @@ -12446,7 +12446,7 @@ snapshots: ci-info: 4.0.0 esbuild: 0.28.0 faster-babel-types: 0.1.0(@babel/types@7.26.0) - hono: 4.12.15 + hono: 4.12.16 isomorphic-fetch: 3.0.0 nanoid: 5.1.9 ohash: 1.1.4 @@ -16346,7 +16346,7 @@ snapshots: highlightjs-vue@1.0.0: {} - hono@4.12.15: {} + hono@4.12.16: {} hook-std@4.0.0: {} From 04daeba0893ffbe7823d3afaaa8ed22eda92245d Mon Sep 17 00:00:00 2001 From: "homarr-renovate[bot]" <158783068+homarr-renovate[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 10:55:08 +0000 Subject: [PATCH 04/16] chore(deps): update dependency jsdom to ^29.1.1 (#5643) Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 55 ++++++++++++++++++++++++++++++++++++--------- pnpm-workspace.yaml | 2 +- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index deb1c5079..78d6fe81b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -406,8 +406,8 @@ catalogs: specifier: ^2.19.1 version: 2.19.1 jsdom: - specifier: ^29.1.0 - version: 29.1.0 + specifier: ^29.1.1 + version: 29.1.1 json5: specifier: ^2.2.3 version: 2.2.3 @@ -639,7 +639,7 @@ importers: version: 10.1.0 jsdom: specifier: 'catalog:' - version: 29.1.0(@noble/hashes@2.0.1) + version: 29.1.1(@noble/hashes@2.0.1) json5: specifier: 'catalog:' version: 2.2.3 @@ -663,7 +663,7 @@ importers: version: 6.1.1(typescript@5.9.3)(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) vitest: specifier: 'catalog:' - version: 4.1.5(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(@vitest/ui@4.1.5)(jsdom@29.1.0(@noble/hashes@2.0.1))(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.5(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(@vitest/ui@4.1.5)(jsdom@29.1.1(@noble/hashes@2.0.1))(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) apps/nextjs: dependencies: @@ -8209,6 +8209,15 @@ packages: canvas: optional: true + jsdom@29.1.1: + resolution: {integrity: sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + jsep@1.4.0: resolution: {integrity: sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==} engines: {node: '>= 10.16.0'} @@ -14250,7 +14259,7 @@ snapshots: obug: 2.1.1 std-env: 4.0.0 tinyrainbow: 3.1.0 - vitest: 4.1.5(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(@vitest/ui@4.1.5)(jsdom@29.1.0(@noble/hashes@2.0.1))(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.5(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(@vitest/ui@4.1.5)(jsdom@29.1.1(@noble/hashes@2.0.1))(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@vitest/expect@4.1.5': dependencies: @@ -14296,7 +14305,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.1.0 - vitest: 4.1.5(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(@vitest/ui@4.1.5)(jsdom@29.1.0(@noble/hashes@2.0.1))(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.5(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(@vitest/ui@4.1.5)(jsdom@29.1.1(@noble/hashes@2.0.1))(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@vitest/utils@4.1.5': dependencies: @@ -16356,7 +16365,7 @@ snapshots: hosted-git-info@9.0.2: dependencies: - lru-cache: 11.2.7 + lru-cache: 11.3.5 hpagent@1.2.0: {} @@ -16799,6 +16808,32 @@ snapshots: transitivePeerDependencies: - '@noble/hashes' + jsdom@29.1.1(@noble/hashes@2.0.1): + dependencies: + '@asamuzakjp/css-color': 5.1.11 + '@asamuzakjp/dom-selector': 7.1.1 + '@bramus/specificity': 2.4.2 + '@csstools/css-syntax-patches-for-csstree': 1.1.3(css-tree@3.2.1) + '@exodus/bytes': 1.15.0(@noble/hashes@2.0.1) + css-tree: 3.2.1 + data-urls: 7.0.0(@noble/hashes@2.0.1) + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0(@noble/hashes@2.0.1) + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.3.5 + parse5: 8.0.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.1 + undici: 7.25.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1(@noble/hashes@2.0.1) + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + jsep@1.4.0: {} jsesc@3.0.2: {} @@ -17770,7 +17805,7 @@ snapshots: path-scurry@2.0.2: dependencies: - lru-cache: 11.2.7 + lru-cache: 11.3.5 minipass: 7.1.3 path-type@4.0.0: {} @@ -19843,7 +19878,7 @@ snapshots: tsx: 4.21.0 yaml: 2.8.3 - vitest@4.1.5(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(@vitest/ui@4.1.5)(jsdom@29.1.0(@noble/hashes@2.0.1))(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + vitest@4.1.5(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(@vitest/ui@4.1.5)(jsdom@29.1.1(@noble/hashes@2.0.1))(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@vitest/expect': 4.1.5 '@vitest/mocker': 4.1.5(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) @@ -19869,7 +19904,7 @@ snapshots: '@types/node': 24.12.2 '@vitest/coverage-v8': 4.1.5(vitest@4.1.5) '@vitest/ui': 4.1.5(vitest@4.1.5) - jsdom: 29.1.0(@noble/hashes@2.0.1) + jsdom: 29.1.1(@noble/hashes@2.0.1) transitivePeerDependencies: - msw diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 92b579459..ba61cd01b 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -136,7 +136,7 @@ catalog: ioredis: 5.10.1 isomorphic-dompurify: ^3.11.0 jotai: ^2.19.1 - jsdom: ^29.1.0 + jsdom: ^29.1.1 json5: ^2.2.3 ldapts: 8.1.7 lodash.clonedeep: ^4.5.0 From e06abbc176f791518fabfc897da424e4ff393ccd Mon Sep 17 00:00:00 2001 From: "homarr-renovate[bot]" <158783068+homarr-renovate[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 21:10:38 +0000 Subject: [PATCH 05/16] chore(deps): update dependency nanoid@>=4.0.0 <5.0.9 to >=5.1.10 (#5646) Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 0b91e4a5e..3e9753994 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "form-data@>=4.0.0 <4.0.4": ">=4.0.5", "hono@<4.6.5": ">=4.12.16", "linkifyjs@<4.3.2": ">=4.3.2", - "nanoid@>=4.0.0 <5.0.9": ">=5.1.9", + "nanoid@>=4.0.0 <5.0.9": ">=5.1.10", "prismjs@<1.30.0": ">=1.30.0", "proxmox-api>undici": "7.24.6", "react-is": "^19.2.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 78d6fe81b..95a8979bd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -572,7 +572,7 @@ overrides: form-data@>=4.0.0 <4.0.4: '>=4.0.5' hono@<4.6.5: '>=4.12.16' linkifyjs@<4.3.2: '>=4.3.2' - nanoid@>=4.0.0 <5.0.9: '>=5.1.9' + nanoid@>=4.0.0 <5.0.9: '>=5.1.10' prismjs@<1.30.0: '>=1.30.0' proxmox-api>undici: 7.24.6 react-is: ^19.2.5 @@ -8693,8 +8693,8 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanoid@5.1.9: - resolution: {integrity: sha512-ZUvP7KeBLe3OZ1ypw6dI/TzYJuvHP77IM4Ry73waSQTLn8/g8rpdjfyVAh7t1/+FjBtG4lCP42MEbDxOsRpBMw==} + nanoid@5.1.11: + resolution: {integrity: sha512-v+KEsUv2ps74PaSKv0gHTxTCgMXOIfBEbaqa6w6ISIGC7ZsvHN4N9oJ8d4cmf0n5oTzQz2SLmThbQWhjd/8eKg==} engines: {node: ^18 || >=20} hasBin: true @@ -12438,7 +12438,7 @@ snapshots: cli-high: 0.4.3 diff: 5.2.2 effect: 3.21.0 - nanoid: 5.1.9 + nanoid: 5.1.11 recast: 0.23.11 xycolors: 0.1.2 @@ -12457,7 +12457,7 @@ snapshots: faster-babel-types: 0.1.0(@babel/types@7.26.0) hono: 4.12.16 isomorphic-fetch: 3.0.0 - nanoid: 5.1.9 + nanoid: 5.1.11 ohash: 1.1.4 pako: 2.1.0 pathe: 1.1.2 @@ -17406,7 +17406,7 @@ snapshots: nanoid@3.3.11: {} - nanoid@5.1.9: {} + nanoid@5.1.11: {} napi-build-utils@1.0.2: {} From 2f301bb56ae686de358c23a095ea5af6e632c00c Mon Sep 17 00:00:00 2001 From: "homarr-renovate[bot]" <158783068+homarr-renovate[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 03:01:59 +0000 Subject: [PATCH 06/16] chore(deps): update turbo monorepo to ^2.9.7 (#5649) Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 96 ++++++++++++++++++++++----------------------- pnpm-workspace.yaml | 6 +-- 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 95a8979bd..08ed53b8b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -214,8 +214,8 @@ catalogs: specifier: ^11.17.0 version: 11.17.0 '@turbo/gen': - specifier: ^2.9.6 - version: 2.9.6 + specifier: ^2.9.7 + version: 2.9.8 '@types/adm-zip': specifier: 0.5.8 version: 0.5.8 @@ -367,8 +367,8 @@ catalogs: specifier: ^10.1.8 version: 10.1.8 eslint-config-turbo: - specifier: ^2.9.6 - version: 2.9.6 + specifier: ^2.9.7 + version: 2.9.8 eslint-plugin-import: specifier: ^2.32.0 version: 2.32.0 @@ -517,8 +517,8 @@ catalogs: specifier: 4.20.4 version: 4.20.4 turbo: - specifier: ^2.9.6 - version: 2.9.6 + specifier: ^2.9.7 + version: 2.9.8 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -621,7 +621,7 @@ importers: version: 11.14.0 '@turbo/gen': specifier: 'catalog:' - version: 2.9.6(@types/node@24.12.2) + version: 2.9.8(@types/node@24.12.2) '@vitejs/plugin-react': specifier: 'catalog:' version: 5.2.0(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) @@ -654,7 +654,7 @@ importers: version: 11.14.0 turbo: specifier: 'catalog:' - version: 2.9.6 + version: 2.9.8 typescript: specifier: 'catalog:' version: 5.9.3 @@ -2923,7 +2923,7 @@ importers: version: 10.1.8(eslint@10.2.1) eslint-config-turbo: specifier: 'catalog:' - version: 2.9.6(eslint@10.2.1)(turbo@2.9.6) + version: 2.9.8(eslint@10.2.1)(turbo@2.9.8) eslint-plugin-import: specifier: 'catalog:' version: 2.32.0(@typescript-eslint/parser@8.58.2(eslint@10.2.1)(typescript@5.9.3))(eslint@10.2.1) @@ -5524,37 +5524,37 @@ packages: '@tsconfig/svelte@1.0.13': resolution: {integrity: sha512-5lYJP45Xllo4yE/RUBccBT32eBlRDbqN8r1/MIvQbKxW3aFqaYPCNgm8D5V20X4ShHcwvYWNlKg3liDh1MlBoA==} - '@turbo/darwin-64@2.9.6': - resolution: {integrity: sha512-X/56SnVXIQZBLKwniGTwEQTGmtE5brSACnKMBWpY3YafuxVYefrC2acamfjgxP7BG5w3I+6jf0UrLoSzgPcSJg==} + '@turbo/darwin-64@2.9.8': + resolution: {integrity: sha512-zU1P95ygDpsQ+2QHh7CVTqvYwi9UBlhKWzoIyUnP3vUoge7H9SQEzrd8dj+XcTrslAp9Db3vIBcXtMVoTEYDnA==} cpu: [x64] os: [darwin] - '@turbo/darwin-arm64@2.9.6': - resolution: {integrity: sha512-aalBeSl4agT/QtYGDyf/XLajedWzUC9Vg/pm/YO6QQ93vkQ91Vz5uK1ta5RbVRDozQSz4njxUNqRNmOXDzW+qw==} + '@turbo/darwin-arm64@2.9.8': + resolution: {integrity: sha512-nKRFI5ZhCGUi4eXNlrojzWcT/CehMj0raot1WE4lw5qf66ZxZHbRbBqcwNEy+ZLY7RkJJRY+TaU89fuj3BcgGg==} cpu: [arm64] os: [darwin] - '@turbo/gen@2.9.6': - resolution: {integrity: sha512-94E/WMvxidupdafLgh6zmlgeWXx1NAkw0d5Q508xDkrPzUNZEPdweK90Gbs3gNDgNFQ7XntZQqvrsCGggeid5A==} + '@turbo/gen@2.9.8': + resolution: {integrity: sha512-whc1xUXvYQ05etSNs6akwU4KpB1mn8q8GIVTz+nB0ruTqvN0tdS7gN/l+UZC0hvipoKUPW9IRrUIxljMq8AKWg==} hasBin: true - '@turbo/linux-64@2.9.6': - resolution: {integrity: sha512-YKi05jnNHaD7vevgYwahpzGwbsNNTwzU2c7VZdmdFm7+cGDP4oREUWSsainiMfRqjRuolQxBwRn8wf1jmu+YZA==} + '@turbo/linux-64@2.9.8': + resolution: {integrity: sha512-Wf/kQpVDCaWM3P5d6lKvJnqjYn/ofUBGbT4h4vRFrdC4N6B/nsun03S2kQNJJMXpXg39woeS4CI367RMU3/OAg==} cpu: [x64] os: [linux] - '@turbo/linux-arm64@2.9.6': - resolution: {integrity: sha512-02o/ZS69cOYEDczXvOB2xmyrtzjQ2hVFtWZK1iqxXUfzMmTjZK4UumrfNnjckSg+gqeBfnPRHa0NstA173Ik3g==} + '@turbo/linux-arm64@2.9.8': + resolution: {integrity: sha512-v6S3HuKVoa9CEx16IxKj1i/+crxXx22A9O80zW1350zyUlcX0T/zLOxVf1k+ruK/7ssXnDJVg8uSYOxlYRedlA==} cpu: [arm64] os: [linux] - '@turbo/windows-64@2.9.6': - resolution: {integrity: sha512-wVdQjvnBI15wB6JrA+43CtUtagjIMmX6XYO758oZHAsCNSxqRlJtdyujih0D8OCnwCRWiGWGI63zAxR0hO6s9g==} + '@turbo/windows-64@2.9.8': + resolution: {integrity: sha512-JaefWOJNBazDylAn3f+lLB34XMNu8nEBbgPRP/Ewysg81cBubGfcyyyzpQOGVuMwfaqdNAE/kitG7w3AbJn9/g==} cpu: [x64] os: [win32] - '@turbo/windows-arm64@2.9.6': - resolution: {integrity: sha512-1XUUyWW0W6FTSqGEhU8RHVqb2wP1SPkr7hIvBlMEwH9jr+sJQK5kqeosLJ/QaUv4ecSAd1ZhIrLoW7qslAzT4A==} + '@turbo/windows-arm64@2.9.8': + resolution: {integrity: sha512-Or6ljjB4TiiwCdVKDYWew0SokQ9kep5zruL8P3nbum9WdkH5XA41rQID4Ulc215Z+R3DrB+qXSHPsJjU3/n2ng==} cpu: [arm64] os: [win32] @@ -7183,8 +7183,8 @@ packages: peerDependencies: eslint: '>=7.0.0' - eslint-config-turbo@2.9.6: - resolution: {integrity: sha512-yCZ2YcUKU2KtAO7fjqBQLI7bIc/cIH4lF/yk4ntSRDfsQiRpAtWaGTxqqMPVLZj5JpFfSZGceuM6YFHufJD5iA==} + eslint-config-turbo@2.9.8: + resolution: {integrity: sha512-NEF7KAULL8FYZEhug9jUUakQ+7S9ANe+KVSxzi4QXhuUdJb1+f85zqkL0ssM+hIlRLgOz5rKJiZQEYgmHo9IKg==} peerDependencies: eslint: '>6.6.0' turbo: '>2.0.0' @@ -7241,8 +7241,8 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - eslint-plugin-turbo@2.9.6: - resolution: {integrity: sha512-mntEkRe71izva2mJHZGxH2ccOdySqXiNJf5tdzRabaxhudLCeonPUdTE6aNGOhgVeL1lclxg5/ibp3C/cpe+kQ==} + eslint-plugin-turbo@2.9.8: + resolution: {integrity: sha512-czP2GjPzR6G+BpVEKXNa7Vs45/Zz0QjUEyB+SSLNpVJTqBe331AlE9IxkC2Eh00H6a1hf/Q6BVwf+1WJupyVYA==} peerDependencies: eslint: '>6.6.0' turbo: '>2.0.0' @@ -10530,8 +10530,8 @@ packages: resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} - turbo@2.9.6: - resolution: {integrity: sha512-+v2QJey7ZUeUiuigkU+uFfklvNUyPI2VO2vBpMYJA+a1hKFLFiKtUYlRHdb3P9CrAvMzi0upbjI4WT+zKtqkBg==} + turbo@2.9.8: + resolution: {integrity: sha512-REEB2rVTVDTf4hav1gJ5dIsGylWZrNonvjXFtk1dCi8gND3PhZtnYkyry1bra/Fo+iP6ctTEZbg6vWfdfHq/1A==} hasBin: true tweetnacl@0.14.5: @@ -13835,29 +13835,29 @@ snapshots: '@tsconfig/svelte@1.0.13': {} - '@turbo/darwin-64@2.9.6': + '@turbo/darwin-64@2.9.8': optional: true - '@turbo/darwin-arm64@2.9.6': + '@turbo/darwin-arm64@2.9.8': optional: true - '@turbo/gen@2.9.6(@types/node@24.12.2)': + '@turbo/gen@2.9.8(@types/node@24.12.2)': dependencies: '@inquirer/prompts': 7.10.1(@types/node@24.12.2) esbuild: 0.25.12 transitivePeerDependencies: - '@types/node' - '@turbo/linux-64@2.9.6': + '@turbo/linux-64@2.9.8': optional: true - '@turbo/linux-arm64@2.9.6': + '@turbo/linux-arm64@2.9.8': optional: true - '@turbo/windows-64@2.9.6': + '@turbo/windows-64@2.9.8': optional: true - '@turbo/windows-arm64@2.9.6': + '@turbo/windows-arm64@2.9.8': optional: true '@types/adm-zip@0.5.8': @@ -15679,11 +15679,11 @@ snapshots: dependencies: eslint: 10.2.1 - eslint-config-turbo@2.9.6(eslint@10.2.1)(turbo@2.9.6): + eslint-config-turbo@2.9.8(eslint@10.2.1)(turbo@2.9.8): dependencies: eslint: 10.2.1 - eslint-plugin-turbo: 2.9.6(eslint@10.2.1)(turbo@2.9.6) - turbo: 2.9.6 + eslint-plugin-turbo: 2.9.8(eslint@10.2.1)(turbo@2.9.8) + turbo: 2.9.8 eslint-import-resolver-node@0.3.9: dependencies: @@ -15783,11 +15783,11 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 - eslint-plugin-turbo@2.9.6(eslint@10.2.1)(turbo@2.9.6): + eslint-plugin-turbo@2.9.8(eslint@10.2.1)(turbo@2.9.8): dependencies: dotenv: 16.0.3 eslint: 10.2.1 - turbo: 2.9.6 + turbo: 2.9.8 eslint-scope@5.1.1: dependencies: @@ -19519,14 +19519,14 @@ snapshots: tunnel@0.0.6: {} - turbo@2.9.6: + turbo@2.9.8: optionalDependencies: - '@turbo/darwin-64': 2.9.6 - '@turbo/darwin-arm64': 2.9.6 - '@turbo/linux-64': 2.9.6 - '@turbo/linux-arm64': 2.9.6 - '@turbo/windows-64': 2.9.6 - '@turbo/windows-arm64': 2.9.6 + '@turbo/darwin-64': 2.9.8 + '@turbo/darwin-arm64': 2.9.8 + '@turbo/linux-64': 2.9.8 + '@turbo/linux-arm64': 2.9.8 + '@turbo/windows-64': 2.9.8 + '@turbo/windows-arm64': 2.9.8 tweetnacl@0.14.5: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ba61cd01b..d98c168b0 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -72,7 +72,7 @@ catalog: "@trpc/react-query": ^11.17.0 "@trpc/server": ^11.17.0 "@trpc/tanstack-react-query": ^11.17.0 - "@turbo/gen": ^2.9.6 + "@turbo/gen": ^2.9.7 "@types/adm-zip": 0.5.8 "@types/bcrypt": 6.0.0 "@types/better-sqlite3": 7.6.13 @@ -123,7 +123,7 @@ catalog: esbuild: ^0.28.0 eslint: ^10.2.1 eslint-config-prettier: ^10.1.8 - eslint-config-turbo: ^2.9.6 + eslint-config-turbo: ^2.9.7 eslint-plugin-import: ^2.32.0 eslint-plugin-jsx-a11y: ^6.10.2 eslint-plugin-react: ^7.37.5 @@ -173,7 +173,7 @@ catalog: trpc-to-openapi: ^3.2.0 tsdav: ^2.2.0 tsx: 4.20.4 - turbo: ^2.9.6 + turbo: ^2.9.7 typescript: ^5.9.3 typescript-eslint: ^8.58.2 undici: 7.24.6 From 3c7a7615badddc8b232a620d61d4a0e7dbce5366 Mon Sep 17 00:00:00 2001 From: "homarr-renovate[bot]" <158783068+homarr-renovate[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 06:13:36 +0000 Subject: [PATCH 07/16] chore(deps): update dependency nanoid@>=4.0.0 <5.0.9 to >=5.1.11 (#5648) Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 3e9753994..67f4b654f 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "form-data@>=4.0.0 <4.0.4": ">=4.0.5", "hono@<4.6.5": ">=4.12.16", "linkifyjs@<4.3.2": ">=4.3.2", - "nanoid@>=4.0.0 <5.0.9": ">=5.1.10", + "nanoid@>=4.0.0 <5.0.9": ">=5.1.11", "prismjs@<1.30.0": ">=1.30.0", "proxmox-api>undici": "7.24.6", "react-is": "^19.2.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 08ed53b8b..e555c315c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -572,7 +572,7 @@ overrides: form-data@>=4.0.0 <4.0.4: '>=4.0.5' hono@<4.6.5: '>=4.12.16' linkifyjs@<4.3.2: '>=4.3.2' - nanoid@>=4.0.0 <5.0.9: '>=5.1.10' + nanoid@>=4.0.0 <5.0.9: '>=5.1.11' prismjs@<1.30.0: '>=1.30.0' proxmox-api>undici: 7.24.6 react-is: ^19.2.5 @@ -5843,6 +5843,7 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + deprecated: Potential CWE-502 - Update to 1.3.1 or higher '@videojs/http-streaming@3.17.4': resolution: {integrity: sha512-XAvdG2dolBuV2Fx8bu1kjmQ2D4TonGzZH68Pgv/O9xMSFWdZtITSMFismeQLEAtMmGwze8qNJp3RgV+jStrJqg==} From 6671ac48f9615922ab88fec0ab5dcd26c22d5e8e Mon Sep 17 00:00:00 2001 From: "homarr-renovate[bot]" <158783068+homarr-renovate[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 09:30:45 +0000 Subject: [PATCH 08/16] chore(deps): update tanstack-query monorepo to ^5.100.7 (#5651) Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 88 ++++++++++++++++++++++----------------------- pnpm-workspace.yaml | 6 ++-- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e555c315c..722afcdae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -133,14 +133,14 @@ catalogs: specifier: ^3.41.1 version: 3.41.1 '@tanstack/react-query': - specifier: ^5.100.6 - version: 5.100.6 + specifier: ^5.100.7 + version: 5.100.8 '@tanstack/react-query-devtools': - specifier: ^5.100.6 - version: 5.100.6 + specifier: ^5.100.7 + version: 5.100.8 '@tanstack/react-query-next-experimental': - specifier: ^5.100.6 - version: 5.100.6 + specifier: ^5.100.7 + version: 5.100.8 '@testcontainers/mysql': specifier: ^11.14.0 version: 11.14.0 @@ -792,22 +792,22 @@ importers: version: 3.41.1(react@19.2.5) '@tanstack/react-query': specifier: 'catalog:' - version: 5.100.6(react@19.2.5) + version: 5.100.8(react@19.2.5) '@tanstack/react-query-devtools': specifier: 'catalog:' - version: 5.100.6(@tanstack/react-query@5.100.6(react@19.2.5))(react@19.2.5) + version: 5.100.8(@tanstack/react-query@5.100.8(react@19.2.5))(react@19.2.5) '@tanstack/react-query-next-experimental': specifier: 'catalog:' - version: 5.100.6(@tanstack/react-query@5.100.6(react@19.2.5))(next@16.2.4(@babel/core@7.26.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(sass@1.99.0))(react@19.2.5) + version: 5.100.8(@tanstack/react-query@5.100.8(react@19.2.5))(next@16.2.4(@babel/core@7.26.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(sass@1.99.0))(react@19.2.5) '@trpc/client': specifier: 'catalog:' version: 11.17.0(@trpc/server@11.17.0(typescript@5.9.3))(typescript@5.9.3) '@trpc/next': specifier: 'catalog:' - version: 11.17.0(@tanstack/react-query@5.100.6(react@19.2.5))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/react-query@11.17.0(@tanstack/react-query@5.100.6(react@19.2.5))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.17.0(typescript@5.9.3))(react@19.2.5)(typescript@5.9.3))(@trpc/server@11.17.0(typescript@5.9.3))(next@16.2.4(@babel/core@7.26.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(sass@1.99.0))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@5.9.3) + version: 11.17.0(@tanstack/react-query@5.100.8(react@19.2.5))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/react-query@11.17.0(@tanstack/react-query@5.100.8(react@19.2.5))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.17.0(typescript@5.9.3))(react@19.2.5)(typescript@5.9.3))(@trpc/server@11.17.0(typescript@5.9.3))(next@16.2.4(@babel/core@7.26.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(sass@1.99.0))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@5.9.3) '@trpc/react-query': specifier: 'catalog:' - version: 11.17.0(@tanstack/react-query@5.100.6(react@19.2.5))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.17.0(typescript@5.9.3))(react@19.2.5)(typescript@5.9.3) + version: 11.17.0(@tanstack/react-query@5.100.8(react@19.2.5))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.17.0(typescript@5.9.3))(react@19.2.5)(typescript@5.9.3) '@trpc/server': specifier: 'catalog:' version: 11.17.0(typescript@5.9.3) @@ -1204,19 +1204,19 @@ importers: version: 1.4.0 '@tanstack/react-query': specifier: 'catalog:' - version: 5.100.6(react@19.2.5) + version: 5.100.8(react@19.2.5) '@trpc/client': specifier: 'catalog:' version: 11.17.0(@trpc/server@11.17.0(typescript@5.9.3))(typescript@5.9.3) '@trpc/react-query': specifier: 'catalog:' - version: 11.17.0(@tanstack/react-query@5.100.6(react@19.2.5))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.17.0(typescript@5.9.3))(react@19.2.5)(typescript@5.9.3) + version: 11.17.0(@tanstack/react-query@5.100.8(react@19.2.5))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.17.0(typescript@5.9.3))(react@19.2.5)(typescript@5.9.3) '@trpc/server': specifier: 'catalog:' version: 11.17.0(typescript@5.9.3) '@trpc/tanstack-react-query': specifier: 'catalog:' - version: 11.17.0(@tanstack/react-query@5.100.6(react@19.2.5))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.17.0(typescript@5.9.3))(react@19.2.5)(typescript@5.9.3) + version: 11.17.0(@tanstack/react-query@5.100.8(react@19.2.5))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.17.0(typescript@5.9.3))(react@19.2.5)(typescript@5.9.3) lodash.clonedeep: specifier: 'catalog:' version: 4.5.0 @@ -1519,7 +1519,7 @@ importers: version: link:../cron-jobs '@tanstack/react-query': specifier: 'catalog:' - version: 5.100.6(react@19.2.5) + version: 5.100.8(react@19.2.5) '@trpc/client': specifier: 'catalog:' version: 11.17.0(@trpc/server@11.17.0(typescript@5.9.3))(typescript@5.9.3) @@ -1528,7 +1528,7 @@ importers: version: 11.17.0(typescript@5.9.3) '@trpc/tanstack-react-query': specifier: 'catalog:' - version: 11.17.0(@tanstack/react-query@5.100.6(react@19.2.5))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.17.0(typescript@5.9.3))(react@19.2.5)(typescript@5.9.3) + version: 11.17.0(@tanstack/react-query@5.100.8(react@19.2.5))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.17.0(typescript@5.9.3))(react@19.2.5)(typescript@5.9.3) react: specifier: 'catalog:' version: 19.2.5 @@ -5200,27 +5200,27 @@ packages: resolution: {integrity: sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg==} engines: {node: '>=12'} - '@tanstack/query-core@5.100.6': - resolution: {integrity: sha512-Os2CPUr98to98RYm+D4qGqGkiffn7MGSyl2547a4MljVkHE30AMJRqTiyCqBfMwzAx/I91vCkAxp5tHSla6Twg==} + '@tanstack/query-core@5.100.8': + resolution: {integrity: sha512-ceYwSFOqjPwET5TA6IOYxzxlGc0ekyH/gfOtWkP0PX43rzX9bxW48Iuw8KAduKCToi4rJAQ6nRy2kAe8gszdmg==} - '@tanstack/query-devtools@5.100.6': - resolution: {integrity: sha512-2SiNwlOiAdTbqktCSmwlXZH8x8mckSbES2O0bdr3qZNhdQl5DCtImZx0S3HGeNHWTIkzTaHx2Isg+bD4M3WRIg==} + '@tanstack/query-devtools@5.100.8': + resolution: {integrity: sha512-29D6k564h7eudwNdfRcq6Je2VFUWGxHwADPg1xC2yHxrovYBwZiqzIv/DkPRsK/EMoOIPIvPq+IU0uCxiQXYPA==} - '@tanstack/react-query-devtools@5.100.6': - resolution: {integrity: sha512-sz3ksMKA2t1rx0+Odzb0x1A3pXH/SVf7fzlzd3sKXzwXz8980f5sbOwfQD6+UfTG8G4Y2KaIg9e3sBn+uC4VTg==} + '@tanstack/react-query-devtools@5.100.8': + resolution: {integrity: sha512-BKpysWo1u3kVMtv92XOv/Gu6eCbE/IxBLJPs0GG/qyySUQvZI2h7mqRwyf8Aa6WfUoX8Yf2AAh0uugQLAr8KtQ==} peerDependencies: - '@tanstack/react-query': ^5.100.6 + '@tanstack/react-query': ^5.100.8 react: ^18 || ^19 - '@tanstack/react-query-next-experimental@5.100.6': - resolution: {integrity: sha512-LqEhb6BG/KgwOEKdnKsBpiB96fudmKnrEYcfJxE27gBReBpuJZZGcllEtYXt6S8TFS2uIwmks4zNnRcspFOoBQ==} + '@tanstack/react-query-next-experimental@5.100.8': + resolution: {integrity: sha512-B1p5yeqWeLrWasZ0s2pG7b7do37XNXIlzHnj8RcEsoJ8DsGWXvAfSjXgnLvG0HdZUTPWjHrVRMIJ0Pgmy+bCtw==} peerDependencies: - '@tanstack/react-query': ^5.100.6 + '@tanstack/react-query': ^5.100.8 next: ^13 || ^14 || ^15 || ^16 react: ^18 || ^19 - '@tanstack/react-query@5.100.6': - resolution: {integrity: sha512-uVSrps0PV16Cxmcn2rvL+dUhwTpTUtiRW347AEeYxMZXO2pZe9ja7E24PAMGoQ5u2g89DD8u4QhOviBk+RN8RA==} + '@tanstack/react-query@5.100.8': + resolution: {integrity: sha512-iNNEekixXU5vtAGKKZX2lx3jTooG5yNY+kv0wSgEdEYG0Mj0JM5bcuQtC35ZAP3nDopT6jciUK3xeX65U7AnfA==} peerDependencies: react: ^18 || ^19 @@ -13511,25 +13511,25 @@ snapshots: dependencies: remove-accents: 0.5.0 - '@tanstack/query-core@5.100.6': {} + '@tanstack/query-core@5.100.8': {} - '@tanstack/query-devtools@5.100.6': {} + '@tanstack/query-devtools@5.100.8': {} - '@tanstack/react-query-devtools@5.100.6(@tanstack/react-query@5.100.6(react@19.2.5))(react@19.2.5)': + '@tanstack/react-query-devtools@5.100.8(@tanstack/react-query@5.100.8(react@19.2.5))(react@19.2.5)': dependencies: - '@tanstack/query-devtools': 5.100.6 - '@tanstack/react-query': 5.100.6(react@19.2.5) + '@tanstack/query-devtools': 5.100.8 + '@tanstack/react-query': 5.100.8(react@19.2.5) react: 19.2.5 - '@tanstack/react-query-next-experimental@5.100.6(@tanstack/react-query@5.100.6(react@19.2.5))(next@16.2.4(@babel/core@7.26.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(sass@1.99.0))(react@19.2.5)': + '@tanstack/react-query-next-experimental@5.100.8(@tanstack/react-query@5.100.8(react@19.2.5))(next@16.2.4(@babel/core@7.26.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(sass@1.99.0))(react@19.2.5)': dependencies: - '@tanstack/react-query': 5.100.6(react@19.2.5) + '@tanstack/react-query': 5.100.8(react@19.2.5) next: 16.2.4(@babel/core@7.26.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(sass@1.99.0) react: 19.2.5 - '@tanstack/react-query@5.100.6(react@19.2.5)': + '@tanstack/react-query@5.100.8(react@19.2.5)': dependencies: - '@tanstack/query-core': 5.100.6 + '@tanstack/query-core': 5.100.8 react: 19.2.5 '@tanstack/react-table@8.20.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': @@ -13802,7 +13802,7 @@ snapshots: '@trpc/server': 11.17.0(typescript@5.9.3) typescript: 5.9.3 - '@trpc/next@11.17.0(@tanstack/react-query@5.100.6(react@19.2.5))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/react-query@11.17.0(@tanstack/react-query@5.100.6(react@19.2.5))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.17.0(typescript@5.9.3))(react@19.2.5)(typescript@5.9.3))(@trpc/server@11.17.0(typescript@5.9.3))(next@16.2.4(@babel/core@7.26.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(sass@1.99.0))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@5.9.3)': + '@trpc/next@11.17.0(@tanstack/react-query@5.100.8(react@19.2.5))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/react-query@11.17.0(@tanstack/react-query@5.100.8(react@19.2.5))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.17.0(typescript@5.9.3))(react@19.2.5)(typescript@5.9.3))(@trpc/server@11.17.0(typescript@5.9.3))(next@16.2.4(@babel/core@7.26.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(sass@1.99.0))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@5.9.3)': dependencies: '@trpc/client': 11.17.0(@trpc/server@11.17.0(typescript@5.9.3))(typescript@5.9.3) '@trpc/server': 11.17.0(typescript@5.9.3) @@ -13811,12 +13811,12 @@ snapshots: react-dom: 19.2.5(react@19.2.5) typescript: 5.9.3 optionalDependencies: - '@tanstack/react-query': 5.100.6(react@19.2.5) - '@trpc/react-query': 11.17.0(@tanstack/react-query@5.100.6(react@19.2.5))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.17.0(typescript@5.9.3))(react@19.2.5)(typescript@5.9.3) + '@tanstack/react-query': 5.100.8(react@19.2.5) + '@trpc/react-query': 11.17.0(@tanstack/react-query@5.100.8(react@19.2.5))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.17.0(typescript@5.9.3))(react@19.2.5)(typescript@5.9.3) - '@trpc/react-query@11.17.0(@tanstack/react-query@5.100.6(react@19.2.5))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.17.0(typescript@5.9.3))(react@19.2.5)(typescript@5.9.3)': + '@trpc/react-query@11.17.0(@tanstack/react-query@5.100.8(react@19.2.5))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.17.0(typescript@5.9.3))(react@19.2.5)(typescript@5.9.3)': dependencies: - '@tanstack/react-query': 5.100.6(react@19.2.5) + '@tanstack/react-query': 5.100.8(react@19.2.5) '@trpc/client': 11.17.0(@trpc/server@11.17.0(typescript@5.9.3))(typescript@5.9.3) '@trpc/server': 11.17.0(typescript@5.9.3) react: 19.2.5 @@ -13826,9 +13826,9 @@ snapshots: dependencies: typescript: 5.9.3 - '@trpc/tanstack-react-query@11.17.0(@tanstack/react-query@5.100.6(react@19.2.5))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.17.0(typescript@5.9.3))(react@19.2.5)(typescript@5.9.3)': + '@trpc/tanstack-react-query@11.17.0(@tanstack/react-query@5.100.8(react@19.2.5))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.17.0(typescript@5.9.3))(react@19.2.5)(typescript@5.9.3)': dependencies: - '@tanstack/react-query': 5.100.6(react@19.2.5) + '@tanstack/react-query': 5.100.8(react@19.2.5) '@trpc/client': 11.17.0(@trpc/server@11.17.0(typescript@5.9.3))(typescript@5.9.3) '@trpc/server': 11.17.0(typescript@5.9.3) react: 19.2.5 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index d98c168b0..313014355 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -45,9 +45,9 @@ catalog: "@semantic-release/release-notes-generator": ^14.1.0 "@t3-oss/env-nextjs": ^0.13.11 "@tabler/icons-react": ^3.41.1 - "@tanstack/react-query": ^5.100.6 - "@tanstack/react-query-devtools": ^5.100.6 - "@tanstack/react-query-next-experimental": ^5.100.6 + "@tanstack/react-query": ^5.100.7 + "@tanstack/react-query-devtools": ^5.100.7 + "@tanstack/react-query-next-experimental": ^5.100.7 "@testcontainers/mysql": ^11.14.0 "@testcontainers/postgresql": ^11.14.0 "@testcontainers/redis": ^11.14.0 From 62a476a949ed086715624425aa1e6e3bf6e3bb4f Mon Sep 17 00:00:00 2001 From: "homarr-renovate[bot]" <158783068+homarr-renovate[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 16:35:25 +0000 Subject: [PATCH 09/16] chore(deps): update dependency eslint to ^10.3.0 (#5652) Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 200 +++++++++++++++++++++----------------------- pnpm-workspace.yaml | 2 +- 2 files changed, 98 insertions(+), 104 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 722afcdae..d12858181 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -361,8 +361,8 @@ catalogs: specifier: ^0.28.0 version: 0.28.0 eslint: - specifier: ^10.2.1 - version: 10.2.1 + specifier: ^10.3.0 + version: 10.3.0 eslint-config-prettier: specifier: ^10.1.8 version: 10.1.8 @@ -922,7 +922,7 @@ importers: version: 9.2.1 eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 node-loader: specifier: 'catalog:' version: 2.1.0(webpack@5.94.0) @@ -1025,7 +1025,7 @@ importers: version: 0.28.0 eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 prettier: specifier: 'catalog:' version: 3.8.3 @@ -1089,7 +1089,7 @@ importers: version: 0.28.0 eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 prettier: specifier: 'catalog:' version: 3.8.3 @@ -1135,7 +1135,7 @@ importers: version: link:../../tooling/typescript eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -1250,7 +1250,7 @@ importers: version: link:../../tooling/typescript eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 prettier: specifier: 'catalog:' version: 3.8.3 @@ -1323,7 +1323,7 @@ importers: version: 0.9.2 eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 prettier: specifier: 'catalog:' version: 3.8.3 @@ -1354,7 +1354,7 @@ importers: version: link:../../tooling/typescript eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -1394,7 +1394,7 @@ importers: version: 0.28.0 eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -1446,7 +1446,7 @@ importers: version: link:../../tooling/typescript eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -1501,7 +1501,7 @@ importers: version: 8.20.0 eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -1550,7 +1550,7 @@ importers: version: 19.2.14 eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -1572,7 +1572,7 @@ importers: version: link:../../tooling/typescript eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -1642,7 +1642,7 @@ importers: version: link:../../tooling/typescript eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -1673,7 +1673,7 @@ importers: version: link:../../tooling/typescript eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -1755,7 +1755,7 @@ importers: version: 0.28.0 eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 prettier: specifier: 'catalog:' version: 3.8.3 @@ -1789,7 +1789,7 @@ importers: version: link:../../tooling/typescript eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 tsx: specifier: 'catalog:' version: 4.20.4 @@ -1823,7 +1823,7 @@ importers: version: 4.0.1 eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -1860,7 +1860,7 @@ importers: version: link:../../tooling/typescript eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -1912,7 +1912,7 @@ importers: version: link:../../tooling/typescript eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -1940,7 +1940,7 @@ importers: version: link:../../tooling/typescript eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -1974,7 +1974,7 @@ importers: version: 6.0.0 eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -2074,7 +2074,7 @@ importers: version: 0.4.14 eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -2108,7 +2108,7 @@ importers: version: link:../../tooling/typescript eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -2181,7 +2181,7 @@ importers: version: link:../../tooling/typescript eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -2209,7 +2209,7 @@ importers: version: link:../../tooling/typescript eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -2291,7 +2291,7 @@ importers: version: 0.5.8 eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -2316,7 +2316,7 @@ importers: version: link:../../tooling/typescript eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -2341,7 +2341,7 @@ importers: version: link:../../tooling/typescript eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -2378,7 +2378,7 @@ importers: version: link:../../tooling/typescript eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -2436,7 +2436,7 @@ importers: version: link:../../tooling/typescript eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -2461,7 +2461,7 @@ importers: version: link:../../tooling/typescript eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -2498,7 +2498,7 @@ importers: version: link:../../tooling/typescript eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -2574,7 +2574,7 @@ importers: version: link:../../tooling/typescript eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -2620,7 +2620,7 @@ importers: version: link:../../tooling/typescript eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -2690,7 +2690,7 @@ importers: version: 1.0.5 eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -2721,7 +2721,7 @@ importers: version: link:../../tooling/typescript eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -2908,7 +2908,7 @@ importers: version: 7.3.58 eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -2920,29 +2920,29 @@ importers: version: 16.2.4 eslint-config-prettier: specifier: 'catalog:' - version: 10.1.8(eslint@10.2.1) + version: 10.1.8(eslint@10.3.0) eslint-config-turbo: specifier: 'catalog:' - version: 2.9.8(eslint@10.2.1)(turbo@2.9.8) + version: 2.9.8(eslint@10.3.0)(turbo@2.9.8) eslint-plugin-import: specifier: 'catalog:' - version: 2.32.0(@typescript-eslint/parser@8.58.2(eslint@10.2.1)(typescript@5.9.3))(eslint@10.2.1) + version: 2.32.0(@typescript-eslint/parser@8.58.2(eslint@10.3.0)(typescript@5.9.3))(eslint@10.3.0) eslint-plugin-jsx-a11y: specifier: 'catalog:' - version: 6.10.2(eslint@10.2.1) + version: 6.10.2(eslint@10.3.0) eslint-plugin-react: specifier: 'catalog:' - version: 7.37.5(eslint@10.2.1) + version: 7.37.5(eslint@10.3.0) eslint-plugin-react-hooks: specifier: 'catalog:' - version: 6.1.1(eslint@10.2.1) + version: 6.1.1(eslint@10.3.0) typescript-eslint: specifier: 'catalog:' - version: 8.58.2(eslint@10.2.1)(typescript@5.9.3) + version: 8.58.2(eslint@10.3.0)(typescript@5.9.3) devDependencies: '@eslint/js': specifier: 'catalog:' - version: 10.0.1(eslint@10.2.1) + version: 10.0.1(eslint@10.3.0) '@homarr/prettier-config': specifier: workspace:^0.1.0 version: link:../prettier @@ -2951,7 +2951,7 @@ importers: version: link:../typescript eslint: specifier: 'catalog:' - version: 10.2.1 + version: 10.3.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -7260,16 +7260,12 @@ packages: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-visitor-keys@5.0.0: - resolution: {integrity: sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint-visitor-keys@5.0.1: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.2.1: - resolution: {integrity: sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q==} + eslint@10.3.0: + resolution: {integrity: sha512-XbEXaRva5cF0ZQB8w6MluHA0kZZfV2DuCMJ3ozyEOHLwDpZX2Lmm/7Pp0xdJmI0GL1W05VH5VwIFHEm1Vcw2gw==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: @@ -11817,9 +11813,9 @@ snapshots: '@esbuild/win32-x64@0.28.0': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@10.2.1)': + '@eslint-community/eslint-utils@4.9.1(eslint@10.3.0)': dependencies: - eslint: 10.2.1 + eslint: 10.3.0 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -11840,9 +11836,9 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/js@10.0.1(eslint@10.2.1)': + '@eslint/js@10.0.1(eslint@10.3.0)': optionalDependencies: - eslint: 10.2.1 + eslint: 10.3.0 '@eslint/object-schema@3.0.5': {} @@ -13390,7 +13386,7 @@ snapshots: '@swagger-api/apidom-error': 1.11.0 '@types/ramda': 0.30.2 axios: 1.15.0 - minimatch: 10.2.2 + minimatch: 10.2.5 ramda: 0.30.1 ramda-adjunct: 5.1.0(ramda@0.30.1) optionalDependencies: @@ -14113,15 +14109,15 @@ snapshots: dependencies: '@types/node': 24.12.2 - '@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.1)(typescript@5.9.3))(eslint@10.2.1)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.3.0)(typescript@5.9.3))(eslint@10.3.0)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.58.2(eslint@10.2.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.58.2(eslint@10.3.0)(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.58.2 - '@typescript-eslint/type-utils': 8.58.2(eslint@10.2.1)(typescript@5.9.3) - '@typescript-eslint/utils': 8.58.2(eslint@10.2.1)(typescript@5.9.3) + '@typescript-eslint/type-utils': 8.58.2(eslint@10.3.0)(typescript@5.9.3) + '@typescript-eslint/utils': 8.58.2(eslint@10.3.0)(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.58.2 - eslint: 10.2.1 + eslint: 10.3.0 ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@5.9.3) @@ -14129,14 +14125,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.58.2(eslint@10.2.1)(typescript@5.9.3)': + '@typescript-eslint/parser@8.58.2(eslint@10.3.0)(typescript@5.9.3)': dependencies: '@typescript-eslint/scope-manager': 8.58.2 '@typescript-eslint/types': 8.58.2 '@typescript-eslint/typescript-estree': 8.58.2(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.58.2 debug: 4.4.3 - eslint: 10.2.1 + eslint: 10.3.0 typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -14159,13 +14155,13 @@ snapshots: dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.58.2(eslint@10.2.1)(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.58.2(eslint@10.3.0)(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 8.58.2 '@typescript-eslint/typescript-estree': 8.58.2(typescript@5.9.3) - '@typescript-eslint/utils': 8.58.2(eslint@10.2.1)(typescript@5.9.3) + '@typescript-eslint/utils': 8.58.2(eslint@10.3.0)(typescript@5.9.3) debug: 4.4.3 - eslint: 10.2.1 + eslint: 10.3.0 ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: @@ -14180,7 +14176,7 @@ snapshots: '@typescript-eslint/types': 8.58.2 '@typescript-eslint/visitor-keys': 8.58.2 debug: 4.4.3 - minimatch: 10.2.2 + minimatch: 10.2.5 semver: 7.7.4 tinyglobby: 0.2.15 ts-api-utils: 2.5.0(typescript@5.9.3) @@ -14188,13 +14184,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.58.2(eslint@10.2.1)(typescript@5.9.3)': + '@typescript-eslint/utils@8.58.2(eslint@10.3.0)(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.3.0) '@typescript-eslint/scope-manager': 8.58.2 '@typescript-eslint/types': 8.58.2 '@typescript-eslint/typescript-estree': 8.58.2(typescript@5.9.3) - eslint: 10.2.1 + eslint: 10.3.0 typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -14202,7 +14198,7 @@ snapshots: '@typescript-eslint/visitor-keys@8.58.2': dependencies: '@typescript-eslint/types': 8.58.2 - eslint-visitor-keys: 5.0.0 + eslint-visitor-keys: 5.0.1 '@umami/node@0.4.0': {} @@ -15676,14 +15672,14 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-config-prettier@10.1.8(eslint@10.2.1): + eslint-config-prettier@10.1.8(eslint@10.3.0): dependencies: - eslint: 10.2.1 + eslint: 10.3.0 - eslint-config-turbo@2.9.8(eslint@10.2.1)(turbo@2.9.8): + eslint-config-turbo@2.9.8(eslint@10.3.0)(turbo@2.9.8): dependencies: - eslint: 10.2.1 - eslint-plugin-turbo: 2.9.8(eslint@10.2.1)(turbo@2.9.8) + eslint: 10.3.0 + eslint-plugin-turbo: 2.9.8(eslint@10.3.0)(turbo@2.9.8) turbo: 2.9.8 eslint-import-resolver-node@0.3.9: @@ -15694,17 +15690,17 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.58.2(eslint@10.2.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@10.2.1): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.58.2(eslint@10.3.0)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@10.3.0): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.58.2(eslint@10.2.1)(typescript@5.9.3) - eslint: 10.2.1 + '@typescript-eslint/parser': 8.58.2(eslint@10.3.0)(typescript@5.9.3) + eslint: 10.3.0 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.2(eslint@10.2.1)(typescript@5.9.3))(eslint@10.2.1): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.2(eslint@10.3.0)(typescript@5.9.3))(eslint@10.3.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -15713,9 +15709,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 10.2.1 + eslint: 10.3.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.58.2(eslint@10.2.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@10.2.1) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.58.2(eslint@10.3.0)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@10.3.0) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -15727,13 +15723,13 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.58.2(eslint@10.2.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.58.2(eslint@10.3.0)(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-jsx-a11y@6.10.2(eslint@10.2.1): + eslint-plugin-jsx-a11y@6.10.2(eslint@10.3.0): dependencies: aria-query: 5.3.2 array-includes: 3.1.9 @@ -15743,7 +15739,7 @@ snapshots: axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - eslint: 10.2.1 + eslint: 10.3.0 hasown: 2.0.2 jsx-ast-utils: 3.3.5 language-tags: 1.0.9 @@ -15752,17 +15748,17 @@ snapshots: safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 - eslint-plugin-react-hooks@6.1.1(eslint@10.2.1): + eslint-plugin-react-hooks@6.1.1(eslint@10.3.0): dependencies: '@babel/core': 7.29.0 '@babel/parser': 7.29.2 - eslint: 10.2.1 + eslint: 10.3.0 zod: 4.3.6 zod-validation-error: 4.0.2(zod@4.3.6) transitivePeerDependencies: - supports-color - eslint-plugin-react@7.37.5(eslint@10.2.1): + eslint-plugin-react@7.37.5(eslint@10.3.0): dependencies: array-includes: 3.1.9 array.prototype.findlast: 1.2.5 @@ -15770,7 +15766,7 @@ snapshots: array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 es-iterator-helpers: 1.2.1 - eslint: 10.2.1 + eslint: 10.3.0 estraverse: 5.3.0 hasown: 2.0.2 jsx-ast-utils: 3.3.5 @@ -15784,10 +15780,10 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 - eslint-plugin-turbo@2.9.8(eslint@10.2.1)(turbo@2.9.8): + eslint-plugin-turbo@2.9.8(eslint@10.3.0)(turbo@2.9.8): dependencies: dotenv: 16.0.3 - eslint: 10.2.1 + eslint: 10.3.0 turbo: 2.9.8 eslint-scope@5.1.1: @@ -15804,13 +15800,11 @@ snapshots: eslint-visitor-keys@3.4.3: {} - eslint-visitor-keys@5.0.0: {} - eslint-visitor-keys@5.0.1: {} - eslint@10.2.1: + eslint@10.3.0: dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.3.0) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.23.5 '@eslint/config-helpers': 0.5.5 @@ -19593,13 +19587,13 @@ snapshots: dependencies: ts-toolbelt: 9.6.0 - typescript-eslint@8.58.2(eslint@10.2.1)(typescript@5.9.3): + typescript-eslint@8.58.2(eslint@10.3.0)(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.1)(typescript@5.9.3))(eslint@10.2.1)(typescript@5.9.3) - '@typescript-eslint/parser': 8.58.2(eslint@10.2.1)(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.3.0)(typescript@5.9.3))(eslint@10.3.0)(typescript@5.9.3) + '@typescript-eslint/parser': 8.58.2(eslint@10.3.0)(typescript@5.9.3) '@typescript-eslint/typescript-estree': 8.58.2(typescript@5.9.3) - '@typescript-eslint/utils': 8.58.2(eslint@10.2.1)(typescript@5.9.3) - eslint: 10.2.1 + '@typescript-eslint/utils': 8.58.2(eslint@10.3.0)(typescript@5.9.3) + eslint: 10.3.0 typescript: 5.9.3 transitivePeerDependencies: - supports-color diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 313014355..e0200e962 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -121,7 +121,7 @@ catalog: drizzle-orm: ^0.45.2 drizzle-zod: ^0.8.3 esbuild: ^0.28.0 - eslint: ^10.2.1 + eslint: ^10.3.0 eslint-config-prettier: ^10.1.8 eslint-config-turbo: ^2.9.7 eslint-plugin-import: ^2.32.0 From b07a4a0f3e3af384ec477bf5c505a705b003de47 Mon Sep 17 00:00:00 2001 From: Meier Lukas Date: Wed, 6 May 2026 21:23:14 +0200 Subject: [PATCH 10/16] fix(update-checker): hide prereleases on non-prerelease versions (#5645) --- .../src/test/update-checker.spec.ts | 138 +++++++++++++++++ .../request-handler/src/update-checker.ts | 140 ++++++++++++------ 2 files changed, 230 insertions(+), 48 deletions(-) create mode 100644 packages/request-handler/src/test/update-checker.spec.ts diff --git a/packages/request-handler/src/test/update-checker.spec.ts b/packages/request-handler/src/test/update-checker.spec.ts new file mode 100644 index 000000000..4c8c1e94b --- /dev/null +++ b/packages/request-handler/src/test/update-checker.spec.ts @@ -0,0 +1,138 @@ +import type { RestEndpointMethodTypes } from "@octokit/plugin-rest-endpoint-methods"; +import { Octokit } from "octokit"; +import { beforeEach, describe, expect, test, vi } from "vitest"; + +import { getAvailableUpdatesAsync } from "../update-checker"; + +const mockEnv = vi.hoisted(() => ({ + NO_EXTERNAL_CONNECTION: false, +})); + +vi.mock("@homarr/common/env", () => ({ + env: mockEnv, +})); + +vi.mock("octokit", () => { + const mockOctokit = { + rest: { + repos: { + listReleases: vi.fn(), + }, + }, + }; + return { + Octokit: class { + rest = mockOctokit.rest; + }, + }; +}); + +describe("getAvailableUpdatesAsync", () => { + beforeEach(() => { + mockEnv.NO_EXTERNAL_CONNECTION = false; + }); + + test("should return available non pre-release updates", async () => { + // Arrange + const currentVersion = "v1.2.3"; + const releases = fakeReleasesWithPrereleases(); + const listReleasesSpy = vi.spyOn(new Octokit().rest.repos, "listReleases"); + listReleasesSpy.mockReturnValue(Promise.resolve({ data: releases, status: 200, headers: {}, url: "" })); + + // Act + const result = await getAvailableUpdatesAsync(currentVersion); + + // Assert + expect(result.map((update) => update.tagName)).toEqual(["v1.3.0", "v1.2.4"]); + expect(listReleasesSpy).toHaveBeenCalledWith({ + owner: "homarr-labs", + repo: "homarr", + }); + }); + + test("should return all available updates", async () => { + // Arrange + const currentVersion = "v1.2.4-beta.2"; + const releases = fakeReleasesWithPrereleases(); + const listReleasesSpy = vi.spyOn(new Octokit().rest.repos, "listReleases"); + listReleasesSpy.mockReturnValue(Promise.resolve({ data: releases, status: 200, headers: {}, url: "" })); + + // Act + const result = await getAvailableUpdatesAsync(currentVersion); + + // Assert + expect(result.map((update) => update.tagName)).toEqual([ + "v1.3.1-beta.1", + "v1.3.0", + "v1.3.0-beta.1", + "v1.2.4", + "v1.2.4-beta.3", + ]); + expect(listReleasesSpy).toHaveBeenCalledWith({ + owner: "homarr-labs", + repo: "homarr", + query: undefined, + }); + }); + + test("should skip releases with invalid semver tags", async () => { + // Arrange + const currentVersion = "v1.2.3"; + const releases = fakeReleases(["v1.2.4", false], ["invalid-tag", false], ["v1.3.0", false]); + const listReleasesSpy = vi.spyOn(new Octokit().rest.repos, "listReleases"); + listReleasesSpy.mockReturnValue(Promise.resolve({ data: releases, status: 200, headers: {}, url: "" })); + + // Act + const result = await getAvailableUpdatesAsync(currentVersion); + + // Assert + expect(result.map((update) => update.tagName)).toEqual(["v1.3.0", "v1.2.4"]); + }); + + test("should throw error for invalid current version", async () => { + // Arrange + const currentVersion = "invalid-version"; + + // Act + const act = () => getAvailableUpdatesAsync(currentVersion); + + // Assert + await expect(act()).rejects.toThrow("non semantic current version"); + }); + + test("should return empty array when NO_EXTERNAL_CONNECTION=true", async () => { + // Arrange + mockEnv.NO_EXTERNAL_CONNECTION = true; + const currentVersion = "v1.0.0"; + + // Act + const result = await getAvailableUpdatesAsync(currentVersion); + + // Assert + expect(result).toEqual([]); + }); +}); + +const fakeReleases = (...inputs: [string, boolean][]) => inputs.map(fakeRelease); +const fakeRelease = ([tagName, isPrerelease]: [string, boolean]) => + ({ + tag_name: tagName, + html_url: `https://github.com/homarr-labs/homarr/releases/tag/${tagName}`, + name: tagName, + body_html: undefined, + prerelease: isPrerelease, + }) as RestEndpointMethodTypes["repos"]["listReleases"]["response"]["data"][number]; + +const fakeReleasesWithPrereleases = () => + fakeReleases( + ["v1.1.0", false], + ["v1.2.2", false], + ["v1.2.3", false], + ["v1.2.4-beta.1", true], + ["v1.2.4-beta.2", true], + ["v1.2.4-beta.3", true], + ["v1.2.4", false], + ["v1.3.0-beta.1", true], + ["v1.3.0", false], + ["v1.3.1-beta.1", true], + ); diff --git a/packages/request-handler/src/update-checker.ts b/packages/request-handler/src/update-checker.ts index 755f14d3b..bd09ce89d 100644 --- a/packages/request-handler/src/update-checker.ts +++ b/packages/request-handler/src/update-checker.ts @@ -1,10 +1,11 @@ import dayjs from "dayjs"; import { Octokit } from "octokit"; -import { compareSemVer, isValidSemVer } from "semver-parser"; +import { compareSemVer, isValidSemVer, parseSemVer } from "semver-parser"; import { env } from "@homarr/common/env"; import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; import { createLogger } from "@homarr/core/infrastructure/logs"; +import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; import { createChannelWithLatestAndEvents } from "@homarr/redis"; import { createCachedRequestHandler } from "@homarr/request-handler/lib/cached-request-handler"; @@ -16,58 +17,101 @@ export const updateCheckerRequestHandler = createCachedRequestHandler({ queryKey: "homarr-update-checker", cacheDuration: dayjs.duration(1, "hour"), async requestAsync(_) { - if (env.NO_EXTERNAL_CONNECTION) - return { - availableUpdates: [], - }; - - const octokit = new Octokit({ - request: { - fetch: fetchWithTrustedCertificatesAsync, - }, - }); - const releases = await octokit.rest.repos.listReleases({ - owner: "homarr-labs", - repo: "homarr", - }); - - const currentVersion = (packageJson as { version: string }).version; - const availableReleases = []; - - for (const release of releases.data) { - if (!isValidSemVer(release.tag_name)) { - logger.warn("Unable to parse semantic tag. Update check might not work.", { tagName: release.tag_name }); - continue; - } - - availableReleases.push(release); - } - - const availableNewerReleases = availableReleases - .filter((release) => compareSemVer(release.tag_name, currentVersion) > 0) - .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 - { version: availableReleases[0]!.tag_name, currentVersion }, - ); - } else { - logger.debug("Update checker did not find any available updates", { currentVersion }); - } - return { - availableUpdates: availableNewerReleases.map((release) => ({ - name: release.name, - contentHtml: release.body_html, - url: release.html_url, - tagName: release.tag_name, - })), + availableUpdates: await getAvailableUpdatesAsync(packageJson.version), }; }, createRedisChannel() { return createChannelWithLatestAndEvents<{ - availableUpdates: { name: string | null; contentHtml?: string; url: string; tagName: string }[]; + availableUpdates: Update[]; }>("homarr:update"); }, }); + +interface Update { + name: string | null; + contentHtml?: string; + url: string; + tagName: string; + isPrerelease: boolean; +} + +const isPrereleaseTag = (tagName: string) => { + try { + const parsed = parseSemVer(tagName); + return Boolean(parsed.pre?.length); + } catch { + return false; + } +}; + +export const getAvailableUpdatesAsync = async (currentVersion: string) => { + if (env.NO_EXTERNAL_CONNECTION) return []; + + if (!isValidSemVer(currentVersion)) { + throw new ErrorWithMetadata("Unable to check for updates due to non semantic current version", { + currentVersion, + }); + } + + const octokit = new Octokit({ + request: { + fetch: fetchWithTrustedCertificatesAsync, + }, + }); + + const isCurrentPrerelease = isPrereleaseTag(currentVersion); + + const releases = await octokit.rest.repos.listReleases({ + owner: "homarr-labs", + repo: "homarr", + }); + + const { skippedTags, semanticReleases } = releases.data + .map((release) => ({ + name: release.name, + contentHtml: release.body_html, + url: release.html_url, + tagName: release.tag_name, + isPrerelease: release.prerelease, + })) + .reduce( + (prev, curr) => { + if (!isValidSemVer(curr.tagName)) { + prev.skippedTags.push(curr.tagName); + return prev; + } + + prev.semanticReleases.push(curr); + return prev; + }, + { semanticReleases: [] as Update[], skippedTags: [] as string[] }, + ); + + if (skippedTags.length > 0) { + logger.warn( + "Some releases were skipped during the update check because their tag name is not a valid semantic version", + { + skippedTags: skippedTags.join(","), + }, + ); + } + + const availableUpdates = semanticReleases + .filter((release) => isCurrentPrerelease || !release.isPrerelease) + .filter((release) => compareSemVer(release.tagName, currentVersion) > 0) + .sort((releaseA, releaseB) => compareSemVer(releaseB.tagName, releaseA.tagName)); + + if (availableUpdates.length === 0) { + logger.debug("No available updates found", { currentVersion }); + return []; + } + + logger.info("Found available updates", { + version: availableUpdates[0]?.tagName, + count: availableUpdates.length, + currentVersion, + }); + + return availableUpdates; +}; From 71e4526ad50c513307a3cb9c7434a4fbbaafde08 Mon Sep 17 00:00:00 2001 From: "homarr-renovate[bot]" <158783068+homarr-renovate[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 22:04:29 +0000 Subject: [PATCH 11/16] chore(deps): update dependency zod to ^4.4.2 (#5656) Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 15 +++++---------- pnpm-workspace.yaml | 2 +- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d12858181..e8637fe60 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -553,7 +553,7 @@ catalogs: specifier: 2.8.3 version: 2.8.3 zod: - specifier: ^4.4.1 + specifier: ^4.4.2 version: 4.4.2 zod-form-data: specifier: ^3.0.1 @@ -11188,9 +11188,6 @@ packages: peerDependencies: zod: ^3.25.0 || ^4.0.0 - zod@4.3.6: - resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} - zod@4.4.2: resolution: {integrity: sha512-IynmDyxsEsb9RKzO3J9+4SxXnl2FTFSzNBaKKaMV6tsSk0rw9gYw9gs+JFCq/qk2LCZ78KDwyj+Z289TijSkUw==} @@ -15753,8 +15750,8 @@ snapshots: '@babel/core': 7.29.0 '@babel/parser': 7.29.2 eslint: 10.3.0 - zod: 4.3.6 - zod-validation-error: 4.0.2(zod@4.3.6) + zod: 4.4.2 + zod-validation-error: 4.0.2(zod@4.4.2) transitivePeerDependencies: - supports-color @@ -20198,16 +20195,14 @@ snapshots: dependencies: zod: 4.4.2 - zod-validation-error@4.0.2(zod@4.3.6): + zod-validation-error@4.0.2(zod@4.4.2): dependencies: - zod: 4.3.6 + zod: 4.4.2 zod-validation-error@5.0.0(zod@4.4.2): dependencies: zod: 4.4.2 - zod@4.3.6: {} - zod@4.4.2: {} zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index e0200e962..07b0c48fc 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -185,7 +185,7 @@ catalog: ws: ^8.20.0 xml2js: ^0.6.2 yaml: 2.8.3 - zod: ^4.4.1 + zod: ^4.4.2 zod-form-data: ^3.0.1 zod-validation-error: ^5.0.0 catalogMode: strict From 698e551790e13956b0ca23513f35c319e72c5840 Mon Sep 17 00:00:00 2001 From: "homarr-renovate[bot]" <158783068+homarr-renovate[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 06:18:42 +0000 Subject: [PATCH 12/16] chore(deps): update tanstack-query monorepo to ^5.100.8 (#5657) Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 6 +++--- pnpm-workspace.yaml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e8637fe60..eac4080a3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -133,13 +133,13 @@ catalogs: specifier: ^3.41.1 version: 3.41.1 '@tanstack/react-query': - specifier: ^5.100.7 + specifier: ^5.100.8 version: 5.100.8 '@tanstack/react-query-devtools': - specifier: ^5.100.7 + specifier: ^5.100.8 version: 5.100.8 '@tanstack/react-query-next-experimental': - specifier: ^5.100.7 + specifier: ^5.100.8 version: 5.100.8 '@testcontainers/mysql': specifier: ^11.14.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 07b0c48fc..528ada51c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -45,9 +45,9 @@ catalog: "@semantic-release/release-notes-generator": ^14.1.0 "@t3-oss/env-nextjs": ^0.13.11 "@tabler/icons-react": ^3.41.1 - "@tanstack/react-query": ^5.100.7 - "@tanstack/react-query-devtools": ^5.100.7 - "@tanstack/react-query-next-experimental": ^5.100.7 + "@tanstack/react-query": ^5.100.8 + "@tanstack/react-query-devtools": ^5.100.8 + "@tanstack/react-query-next-experimental": ^5.100.8 "@testcontainers/mysql": ^11.14.0 "@testcontainers/postgresql": ^11.14.0 "@testcontainers/redis": ^11.14.0 From 9e2c487389f6d9babba1c45f46fe35e762a2a821 Mon Sep 17 00:00:00 2001 From: "homarr-renovate[bot]" <158783068+homarr-renovate[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 06:19:31 +0000 Subject: [PATCH 13/16] chore(deps): update dependency isomorphic-dompurify to ^3.12.0 (#5658) Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 58 ++++++++++++--------------------------------- pnpm-workspace.yaml | 2 +- 2 files changed, 16 insertions(+), 44 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eac4080a3..11c3875d6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -400,8 +400,8 @@ catalogs: specifier: 5.10.1 version: 5.10.1 isomorphic-dompurify: - specifier: ^3.11.0 - version: 3.11.0 + specifier: ^3.12.0 + version: 3.12.0 jotai: specifier: ^2.19.1 version: 2.19.1 @@ -843,7 +843,7 @@ importers: version: 13.0.6 isomorphic-dompurify: specifier: 'catalog:' - version: 3.11.0(@noble/hashes@2.0.1) + version: 3.12.0(@noble/hashes@2.0.1) jotai: specifier: 'catalog:' version: 2.19.1(@babel/core@7.26.0)(@babel/template@7.28.6)(@types/react@19.2.14)(react@19.2.5) @@ -6896,6 +6896,9 @@ packages: dompurify@3.4.1: resolution: {integrity: sha512-JahakDAIg1gyOm7dlgWSDjV4n7Ip2PKR55NIT6jrMfIgLFgWo81vdr1/QGqWtFNRqXP9UV71oVePtjqS2ebnPw==} + dompurify@3.4.2: + resolution: {integrity: sha512-lHeS9SA/IKeIFFyYciHBr2n0v1VMPlSj843HdLOwjb2OxNwdq9Xykxqhk+FE42MzAdHvInbAolSE4mhahPpjXA==} + dot-prop@5.3.0: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} @@ -8110,8 +8113,8 @@ packages: resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==} engines: {node: '>=18'} - isomorphic-dompurify@3.11.0: - resolution: {integrity: sha512-il0sNhLnfawc6vKoMqnZX1JXzEzw0pP3DZWg7mM7VJNJ8cq6DCxeAjEcfjTazyBF8dkUn+rBsBPS5NQs4ZaI3g==} + isomorphic-dompurify@3.12.0: + resolution: {integrity: sha512-8n+j+6ypTHvriJwFOQ2qusQ6bzGjZVcR3jbe1pBpLcGI1dn4WIl0ctLBngqE5QttquQBAlKXwJeTMw+X7x7qKw==} engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} isomorphic-fetch@3.0.0: @@ -8197,15 +8200,6 @@ packages: jsbn@1.1.0: resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} - jsdom@29.1.0: - resolution: {integrity: sha512-YNUc7fB9QuvSSQWfrH0xF+TyABkxUwx8sswgIDaCrw4Hol8BghdZDkITtZheRJeMtzWlnTfsM3bBBusRvpO1wg==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} - peerDependencies: - canvas: ^3.0.0 - peerDependenciesMeta: - canvas: - optional: true - jsdom@29.1.1: resolution: {integrity: sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==} engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} @@ -15331,6 +15325,10 @@ snapshots: optionalDependencies: '@types/trusted-types': 2.0.7 + dompurify@3.4.2: + optionalDependencies: + '@types/trusted-types': 2.0.7 + dot-prop@5.3.0: dependencies: is-obj: 2.0.0 @@ -16676,10 +16674,10 @@ snapshots: isexe@3.1.5: optional: true - isomorphic-dompurify@3.11.0(@noble/hashes@2.0.1): + isomorphic-dompurify@3.12.0(@noble/hashes@2.0.1): dependencies: - dompurify: 3.4.1 - jsdom: 29.1.0(@noble/hashes@2.0.1) + dompurify: 3.4.2 + jsdom: 29.1.1(@noble/hashes@2.0.1) transitivePeerDependencies: - '@noble/hashes' - canvas @@ -16774,32 +16772,6 @@ snapshots: jsbn@1.1.0: {} - jsdom@29.1.0(@noble/hashes@2.0.1): - dependencies: - '@asamuzakjp/css-color': 5.1.11 - '@asamuzakjp/dom-selector': 7.1.1 - '@bramus/specificity': 2.4.2 - '@csstools/css-syntax-patches-for-csstree': 1.1.3(css-tree@3.2.1) - '@exodus/bytes': 1.15.0(@noble/hashes@2.0.1) - css-tree: 3.2.1 - data-urls: 7.0.0(@noble/hashes@2.0.1) - decimal.js: 10.6.0 - html-encoding-sniffer: 6.0.0(@noble/hashes@2.0.1) - is-potential-custom-element-name: 1.0.1 - lru-cache: 11.3.5 - parse5: 8.0.1 - saxes: 6.0.0 - symbol-tree: 3.2.4 - tough-cookie: 6.0.1 - undici: 7.25.0 - w3c-xmlserializer: 5.0.0 - webidl-conversions: 8.0.1 - whatwg-mimetype: 5.0.0 - whatwg-url: 16.0.1(@noble/hashes@2.0.1) - xml-name-validator: 5.0.0 - transitivePeerDependencies: - - '@noble/hashes' - jsdom@29.1.1(@noble/hashes@2.0.1): dependencies: '@asamuzakjp/css-color': 5.1.11 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 528ada51c..57dccd9b4 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -134,7 +134,7 @@ catalog: glob: ^13.0.6 ical.js: ^2.2.1 ioredis: 5.10.1 - isomorphic-dompurify: ^3.11.0 + isomorphic-dompurify: ^3.12.0 jotai: ^2.19.1 jsdom: ^29.1.1 json5: ^2.2.3 From 663fe980bfe743e091e03a79af6a1ba3fdf72049 Mon Sep 17 00:00:00 2001 From: "homarr-renovate[bot]" <158783068+homarr-renovate[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 09:39:58 +0000 Subject: [PATCH 14/16] chore(deps): update dependency yaml to v2.8.4 (#5659) Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 45 ++++++++++++++++++++++++++------------------- pnpm-workspace.yaml | 2 +- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 11c3875d6..e80d3410c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -550,8 +550,8 @@ catalogs: specifier: ^0.6.2 version: 0.6.2 yaml: - specifier: 2.8.3 - version: 2.8.3 + specifier: 2.8.4 + version: 2.8.4 zod: specifier: ^4.4.2 version: 4.4.2 @@ -624,7 +624,7 @@ importers: version: 2.9.8(@types/node@24.12.2) '@vitejs/plugin-react': specifier: 'catalog:' - version: 5.2.0(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 5.2.0(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4)) '@vitest/coverage-v8': specifier: 'catalog:' version: 4.1.5(vitest@4.1.5) @@ -660,10 +660,10 @@ importers: version: 5.9.3 vite-tsconfig-paths: specifier: 'catalog:' - version: 6.1.1(typescript@5.9.3)(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 6.1.1(typescript@5.9.3)(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4)) vitest: specifier: 'catalog:' - version: 4.1.5(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(@vitest/ui@4.1.5)(jsdom@29.1.1(@noble/hashes@2.0.1))(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.5(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(@vitest/ui@4.1.5)(jsdom@29.1.1(@noble/hashes@2.0.1))(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4)) apps/nextjs: dependencies: @@ -885,7 +885,7 @@ importers: version: 1.8.1(react@19.2.5) yaml: specifier: 'catalog:' - version: 2.8.3 + version: 2.8.4 zod: specifier: 'catalog:' version: 4.4.2 @@ -11116,6 +11116,11 @@ packages: engines: {node: '>= 14.6'} hasBin: true + yaml@2.8.4: + resolution: {integrity: sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==} + engines: {node: '>= 14.6'} + hasBin: true + yargs-parser@20.2.9: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} engines: {node: '>=10'} @@ -14223,7 +14228,7 @@ snapshots: global: 4.4.0 is-function: 1.0.2 - '@vitejs/plugin-react@5.2.0(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + '@vitejs/plugin-react@5.2.0(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) @@ -14231,7 +14236,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-rc.3 '@types/babel__core': 7.20.5 react-refresh: 0.18.0 - vite: 7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4) transitivePeerDependencies: - supports-color @@ -14247,7 +14252,7 @@ snapshots: obug: 2.1.1 std-env: 4.0.0 tinyrainbow: 3.1.0 - vitest: 4.1.5(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(@vitest/ui@4.1.5)(jsdom@29.1.1(@noble/hashes@2.0.1))(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.5(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(@vitest/ui@4.1.5)(jsdom@29.1.1(@noble/hashes@2.0.1))(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4)) '@vitest/expect@4.1.5': dependencies: @@ -14258,13 +14263,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.5(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + '@vitest/mocker@4.1.5(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4))': dependencies: '@vitest/spy': 4.1.5 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4) '@vitest/pretty-format@4.1.5': dependencies: @@ -14293,7 +14298,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.1.0 - vitest: 4.1.5(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(@vitest/ui@4.1.5)(jsdom@29.1.1(@noble/hashes@2.0.1))(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.5(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(@vitest/ui@4.1.5)(jsdom@29.1.1(@noble/hashes@2.0.1))(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4)) '@vitest/utils@4.1.5': dependencies: @@ -19815,17 +19820,17 @@ snapshots: dependencies: global: 4.4.0 - vite-tsconfig-paths@6.1.1(typescript@5.9.3)(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + vite-tsconfig-paths@6.1.1(typescript@5.9.3)(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4)): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.3(typescript@5.9.3) - vite: 7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4) transitivePeerDependencies: - supports-color - typescript - vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): + vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.4) @@ -19840,12 +19845,12 @@ snapshots: sugarss: 5.0.0(postcss@8.5.8) terser: 5.46.1 tsx: 4.21.0 - yaml: 2.8.3 + yaml: 2.8.4 - vitest@4.1.5(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(@vitest/ui@4.1.5)(jsdom@29.1.1(@noble/hashes@2.0.1))(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + vitest@4.1.5(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(@vitest/ui@4.1.5)(jsdom@29.1.1(@noble/hashes@2.0.1))(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4)): dependencies: '@vitest/expect': 4.1.5 - '@vitest/mocker': 4.1.5(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/mocker': 4.1.5(vite@7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4)) '@vitest/pretty-format': 4.1.5 '@vitest/runner': 4.1.5 '@vitest/snapshot': 4.1.5 @@ -19862,7 +19867,7 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.1.0 - vite: 7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 7.1.12(@types/node@24.12.2)(sass@1.99.0)(sugarss@5.0.0(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.12.2 @@ -20109,6 +20114,8 @@ snapshots: yaml@2.8.3: {} + yaml@2.8.4: {} + yargs-parser@20.2.9: {} yargs-parser@21.1.1: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 57dccd9b4..7978ed6f1 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -184,7 +184,7 @@ catalog: winston: 3.19.0 ws: ^8.20.0 xml2js: ^0.6.2 - yaml: 2.8.3 + yaml: 2.8.4 zod: ^4.4.2 zod-form-data: ^3.0.1 zod-validation-error: ^5.0.0 From 0937555d49c29455bc33307ae5e50bf5b812c7c7 Mon Sep 17 00:00:00 2001 From: Meier Lukas Date: Thu, 7 May 2026 16:54:57 +0200 Subject: [PATCH 15/16] feat(entity-state): add data attributes for custom-css styling (#5655) --- packages/widgets/src/smart-home/entity-state/component.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/widgets/src/smart-home/entity-state/component.tsx b/packages/widgets/src/smart-home/entity-state/component.tsx index 587978a42..f11cfa844 100644 --- a/packages/widgets/src/smart-home/entity-state/component.tsx +++ b/packages/widgets/src/smart-home/entity-state/component.tsx @@ -76,6 +76,7 @@ export default function SmartHomeEntityStateWidget({ return ( Date: Thu, 7 May 2026 11:50:58 -0400 Subject: [PATCH 16/16] feat: add Umami Analytics integration and widget (#5362) Co-authored-by: Josh G Co-authored-by: Meier Lukas Co-authored-by: Thomas --- .gitignore | 2 +- packages/api/src/router/widgets/index.ts | 1 + packages/api/src/router/widgets/umami.ts | 205 ++++++++ packages/cron-jobs/src/index.ts | 2 + .../cron-jobs/src/jobs/integrations/umami.ts | 14 + packages/db/migrations/seed.ts | 4 +- packages/definitions/src/integration.ts | 10 + packages/definitions/src/widget.ts | 1 + packages/integrations/src/base/creator.ts | 2 + packages/integrations/src/index.ts | 2 + packages/integrations/src/types.ts | 1 + .../src/umami/umami-integration.ts | 450 ++++++++++++++++++ .../integrations/src/umami/umami-types.ts | 84 ++++ packages/request-handler/src/umami.ts | 84 ++++ packages/translation/src/lang/en.json | 101 ++++ packages/widgets/src/_inputs/index.ts | 6 + .../src/_inputs/widget-umami-event-input.tsx | 46 ++ .../widget-umami-event-names-input.tsx | 44 ++ .../_inputs/widget-umami-website-input.tsx | 44 ++ packages/widgets/src/index.tsx | 2 + packages/widgets/src/options.ts | 15 + packages/widgets/src/umami/component.tsx | 39 ++ packages/widgets/src/umami/index.ts | 88 ++++ packages/widgets/src/umami/umami-content.tsx | 220 +++++++++ .../src/umami/umami-events-content.tsx | 138 ++++++ packages/widgets/src/umami/umami-top-list.tsx | 77 +++ packages/widgets/src/umami/umami-utils.ts | 33 ++ 27 files changed, 1713 insertions(+), 2 deletions(-) create mode 100644 packages/api/src/router/widgets/umami.ts create mode 100644 packages/cron-jobs/src/jobs/integrations/umami.ts create mode 100644 packages/integrations/src/umami/umami-integration.ts create mode 100644 packages/integrations/src/umami/umami-types.ts create mode 100644 packages/request-handler/src/umami.ts create mode 100644 packages/widgets/src/_inputs/widget-umami-event-input.tsx create mode 100644 packages/widgets/src/_inputs/widget-umami-event-names-input.tsx create mode 100644 packages/widgets/src/_inputs/widget-umami-website-input.tsx create mode 100644 packages/widgets/src/umami/component.tsx create mode 100644 packages/widgets/src/umami/index.ts create mode 100644 packages/widgets/src/umami/umami-content.tsx create mode 100644 packages/widgets/src/umami/umami-events-content.tsx create mode 100644 packages/widgets/src/umami/umami-top-list.tsx create mode 100644 packages/widgets/src/umami/umami-utils.ts diff --git a/.gitignore b/.gitignore index 43488df7a..0e991cc6c 100644 --- a/.gitignore +++ b/.gitignore @@ -71,4 +71,4 @@ en.d.json.ts # personal agent config AGENTS.md -.opencode \ No newline at end of file +.opencode diff --git a/packages/api/src/router/widgets/index.ts b/packages/api/src/router/widgets/index.ts index 5e29dfa72..e40c3f000 100644 --- a/packages/api/src/router/widgets/index.ts +++ b/packages/api/src/router/widgets/index.ts @@ -30,4 +30,5 @@ export const widgetRouter = createTRPCRouter({ timetable: lazy(() => import("./timetable").then((mod) => mod.timetableRouter)), tracearr: lazy(() => import("./tracearr").then((mod) => mod.tracearrRouter)), speedtestTracker: lazy(() => import("./speedtest-tracker").then((mod) => mod.speedtestTrackerRouter)), + umami: lazy(() => import("./umami").then((mod) => mod.umamiRouter)), }); diff --git a/packages/api/src/router/widgets/umami.ts b/packages/api/src/router/widgets/umami.ts new file mode 100644 index 000000000..2c240f4bb --- /dev/null +++ b/packages/api/src/router/widgets/umami.ts @@ -0,0 +1,205 @@ +import { observable } from "@trpc/server/observable"; +import { z } from "zod/v4"; + +import { createLogger } from "@homarr/core/infrastructure/logs"; +import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; +import { createIntegrationAsync } from "@homarr/integrations"; +import { + umamiActiveVisitorsRequestHandler, + umamiEventNamesRequestHandler, + umamiMultiEventRequestHandler, + umamiRequestHandler, + umamiTopPagesRequestHandler, + umamiTopReferrersRequestHandler, +} from "@homarr/request-handler/umami"; + +import { createManyIntegrationMiddleware, createOneIntegrationMiddleware } from "../../middlewares/integration"; +import { createTRPCRouter, publicProcedure } from "../../trpc"; + +const logger = createLogger({ module: "umami-router" }); + +export const umamiRouter = createTRPCRouter({ + getWebsites: publicProcedure.concat(createOneIntegrationMiddleware("query", "umami")).query(async ({ ctx }) => { + try { + const instance = await createIntegrationAsync(ctx.integration); + return await instance.getWebsitesAsync(); + } catch (error) { + logger.warn(new Error("Failed to load websites", { cause: error })); + return []; + } + }), + + getVisitorStats: publicProcedure + .input( + z.object({ + websiteId: z.string(), + timeFrame: z.string(), + eventName: z.string().optional(), + }), + ) + .concat(createManyIntegrationMiddleware("query", "umami")) + .query(async ({ ctx, input }) => { + const results = await Promise.all( + ctx.integrations.map(async (integration) => { + const innerHandler = umamiRequestHandler.handler(integration, { + websiteId: input.websiteId, + timeFrame: input.timeFrame, + eventName: input.eventName, + }); + const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + + return { + integrationId: integration.id, + integrationName: integration.name, + integrationUrl: integration.url, + visitorStats: data, + updatedAt: timestamp, + }; + }), + ); + + return results; + }), + + getEventNames: publicProcedure + .input(z.object({ websiteId: z.string() })) + .concat(createOneIntegrationMiddleware("query", "umami")) + .query(async ({ ctx, input }) => { + try { + const innerHandler = umamiEventNamesRequestHandler.handler(ctx.integration, { websiteId: input.websiteId }); + const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + return data; + } catch (error) { + logger.warn( + new ErrorWithMetadata("Failed to load event names", { websiteId: input.websiteId }, { cause: error }), + ); + return []; + } + }), + + getTopPages: publicProcedure + .input( + z.object({ + websiteId: z.string(), + timeFrame: z.string(), + limit: z.number().int().min(1).max(500), + }), + ) + .concat(createOneIntegrationMiddleware("query", "umami")) + .query(async ({ ctx, input }) => { + try { + const innerHandler = umamiTopPagesRequestHandler.handler(ctx.integration, { + websiteId: input.websiteId, + timeFrame: input.timeFrame, + limit: input.limit, + }); + const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + return data; + } catch (error) { + logger.warn( + new ErrorWithMetadata( + "Failed to load top pages", + { websiteId: input.websiteId, timeFrame: input.timeFrame, limit: input.limit }, + { cause: error }, + ), + ); + return []; + } + }), + + getTopReferrers: publicProcedure + .input( + z.object({ + websiteId: z.string(), + timeFrame: z.string(), + limit: z.number().int().min(1).max(500), + }), + ) + .concat(createOneIntegrationMiddleware("query", "umami")) + .query(async ({ ctx, input }) => { + try { + const innerHandler = umamiTopReferrersRequestHandler.handler(ctx.integration, { + websiteId: input.websiteId, + timeFrame: input.timeFrame, + limit: input.limit, + }); + const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + return data; + } catch (error) { + logger.warn( + new ErrorWithMetadata( + "Failed to load top referrers", + { websiteId: input.websiteId, timeFrame: input.timeFrame, limit: input.limit }, + { cause: error }, + ), + ); + return []; + } + }), + + getMultiEventTimeSeries: publicProcedure + .input( + z.object({ + websiteId: z.string(), + timeFrame: z.string(), + eventNames: z.array(z.string()), + }), + ) + .concat(createOneIntegrationMiddleware("query", "umami")) + .query(async ({ ctx, input }) => { + try { + const sortedNames = [...input.eventNames].sort(); + const innerHandler = umamiMultiEventRequestHandler.handler(ctx.integration, { + websiteId: input.websiteId, + timeFrame: input.timeFrame, + eventNames: sortedNames, + }); + const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + return data; + } catch (error) { + logger.warn( + new ErrorWithMetadata( + "Failed to load multi-event time series", + { websiteId: input.websiteId, timeFrame: input.timeFrame, eventNames: JSON.stringify(input.eventNames) }, + { cause: error }, + ), + ); + return []; + } + }), + + getActiveVisitors: publicProcedure + .input(z.object({ websiteId: z.string() })) + .concat(createOneIntegrationMiddleware("query", "umami")) + .query(async ({ ctx, input }) => { + try { + const innerHandler = umamiActiveVisitorsRequestHandler.handler(ctx.integration, { + websiteId: input.websiteId, + }); + const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + return data; + } catch (error) { + logger.warn( + new ErrorWithMetadata("Failed to load active visitors", { websiteId: input.websiteId }, { cause: error }), + ); + return 0; + } + }), + + subscribeActiveVisitors: publicProcedure + .input(z.object({ websiteId: z.string() })) + .concat(createOneIntegrationMiddleware("query", "umami")) + .subscription(({ ctx, input }) => { + return observable((emit) => { + const innerHandler = umamiActiveVisitorsRequestHandler.handler(ctx.integration, { + websiteId: input.websiteId, + }); + const unsubscribe = innerHandler.subscribe((count) => { + emit.next(count); + }); + return () => { + unsubscribe(); + }; + }); + }), +}); diff --git a/packages/cron-jobs/src/index.ts b/packages/cron-jobs/src/index.ts index 7f2b2349f..685fcb448 100644 --- a/packages/cron-jobs/src/index.ts +++ b/packages/cron-jobs/src/index.ts @@ -20,6 +20,7 @@ import { networkControllerJob } from "./jobs/integrations/network-controller"; import { refreshNotificationsJob } from "./jobs/integrations/notifications"; import { timetableJob } from "./jobs/integrations/timetable"; import { tracearrJob } from "./jobs/integrations/tracearr"; +import { umamiActiveVisitorsJob } from "./jobs/integrations/umami"; import { minecraftServerStatusJob } from "./jobs/minecraft-server-status"; import { pingJob } from "./jobs/ping"; import { rssFeedsJob } from "./jobs/rss-feeds"; @@ -54,6 +55,7 @@ export const jobGroup = createCronJobGroup({ weather: weatherJob, timetable: timetableJob, tracearr: tracearrJob, + umamiActiveVisitors: umamiActiveVisitorsJob, }); export type JobGroupKeys = ReturnType<(typeof jobGroup)["getKeys"]>[number]; diff --git a/packages/cron-jobs/src/jobs/integrations/umami.ts b/packages/cron-jobs/src/jobs/integrations/umami.ts new file mode 100644 index 000000000..c50a3c604 --- /dev/null +++ b/packages/cron-jobs/src/jobs/integrations/umami.ts @@ -0,0 +1,14 @@ +import { EVERY_30_SECONDS } from "@homarr/cron-jobs-core/expressions"; +import { createRequestIntegrationJobHandler } from "@homarr/request-handler/lib/cached-request-integration-job-handler"; +import { umamiActiveVisitorsRequestHandler } from "@homarr/request-handler/umami"; + +import { createCronJob } from "../../lib"; + +export const umamiActiveVisitorsJob = createCronJob("umamiActiveVisitors", EVERY_30_SECONDS).withCallback( + createRequestIntegrationJobHandler(umamiActiveVisitorsRequestHandler.handler, { + widgetKinds: ["umami"], + getInput: { + umami: (options) => (options.websiteId ? { websiteId: options.websiteId } : []), + }, + }), +); diff --git a/packages/db/migrations/seed.ts b/packages/db/migrations/seed.ts index 407c03746..a436b19c0 100644 --- a/packages/db/migrations/seed.ts +++ b/packages/db/migrations/seed.ts @@ -4,6 +4,7 @@ import { everyoneGroup, getIntegrationDefaultUrl, getIntegrationName, + integrationDefs, integrationKinds, } from "@homarr/definitions"; import { defaultServerSettings, defaultServerSettingsKeys } from "@homarr/server-settings"; @@ -143,8 +144,9 @@ const seedDefaultIntegrationsAsync = async (db: Database) => { const defaultIntegrations = integrationKinds.reduce((acc, kind) => { const name = getIntegrationName(kind); const defaultUrl = getIntegrationDefaultUrl(kind); + const hasNoAuthOption = integrationDefs[kind].secretKinds.some((kinds) => kinds.length === 0); - if (defaultUrl !== undefined) { + if (defaultUrl !== undefined && hasNoAuthOption) { acc.push({ id: "new", name: `${name} Default`, diff --git a/packages/definitions/src/integration.ts b/packages/definitions/src/integration.ts index 006bc9ab7..81c58b0af 100644 --- a/packages/definitions/src/integration.ts +++ b/packages/definitions/src/integration.ts @@ -370,6 +370,15 @@ export const integrationDefs = { category: ["speedtest"], documentationUrl: createDocumentationLink("/docs/integrations/speedtest-tracker"), }, + umami: { + name: "Umami", + secretKinds: [["username", "password"], ["apiKey"]], + iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/umami.svg", + category: ["analytics"], + defaultUrl: "https://api.umami.is/v1", + // @ts-expect-error - docs page will be created when integration is merged + documentationUrl: createDocumentationLink("/docs/integrations/umami"), + }, // This integration only returns mock data, it is used during development (but can also be used in production by directly going to the create page) mock: { name: "Mock", @@ -461,6 +470,7 @@ export const integrationCategories = [ "notes", "mediaMonitoring", "speedtest", + "analytics", ] as const; export type IntegrationCategory = (typeof integrationCategories)[number]; diff --git a/packages/definitions/src/widget.ts b/packages/definitions/src/widget.ts index b246b32ea..d6305a08e 100644 --- a/packages/definitions/src/widget.ts +++ b/packages/definitions/src/widget.ts @@ -37,5 +37,6 @@ export const widgetKinds = [ "immich-albumCarousel", "tracearr", "speedtestTracker", + "umami", ] as const; export type WidgetKind = (typeof widgetKinds)[number]; diff --git a/packages/integrations/src/base/creator.ts b/packages/integrations/src/base/creator.ts index 5748bdd96..9e2466bea 100644 --- a/packages/integrations/src/base/creator.ts +++ b/packages/integrations/src/base/creator.ts @@ -46,6 +46,7 @@ import { SeerrIntegration } from "../seerr/seerr-integration"; import { SpeedtestTrackerIntegration } from "../speedtest-tracker/speedtest-tracker-integration"; import { TracearrIntegration } from "../tracearr/tracearr-integration"; import { TrueNasIntegration } from "../truenas/truenas-integration"; +import { UmamiIntegration } from "../umami/umami-integration"; import { UnifiControllerIntegration } from "../unifi-controller/unifi-controller-integration"; import { UnraidIntegration } from "../unraid/unraid-integration"; import type { Integration, IntegrationInput } from "./integration"; @@ -121,6 +122,7 @@ export const integrationCreators = { searchCh: SearchChIntegration, immich: ImmichIntegration, speedtestTracker: SpeedtestTrackerIntegration, + umami: UmamiIntegration, } satisfies Record Promise]>; type IntegrationInstanceOfKind = { diff --git a/packages/integrations/src/index.ts b/packages/integrations/src/index.ts index 2ac590bdb..c7451265c 100644 --- a/packages/integrations/src/index.ts +++ b/packages/integrations/src/index.ts @@ -33,6 +33,7 @@ export { CoolifyIntegration } from "./coolify/coolify-integration"; export { ImmichIntegration } from "./immich/immich-integration"; export { TracearrIntegration } from "./tracearr/tracearr-integration"; export { SpeedtestTrackerIntegration } from "./speedtest-tracker/speedtest-tracker-integration"; +export { UmamiIntegration } from "./umami/umami-integration"; // Types export type { IntegrationInput } from "./base/integration"; @@ -68,6 +69,7 @@ export type { } from "./anchor/anchor-types"; export type { TracearrDashboardData } from "./tracearr/tracearr-types"; export type { SpeedtestTrackerDashboardData } from "./speedtest-tracker/speedtest-tracker-types"; +export type { UmamiVisitorStats } from "./umami/umami-types"; // Schemas export { anchorNotesListInputSchema } from "./anchor/anchor-types"; diff --git a/packages/integrations/src/types.ts b/packages/integrations/src/types.ts index eb8b9e6ca..10ae96ee4 100644 --- a/packages/integrations/src/types.ts +++ b/packages/integrations/src/types.ts @@ -16,3 +16,4 @@ export * from "./anchor/anchor-types"; export * from "./interfaces/timetable/timetable-types"; export * from "./tracearr/tracearr-types"; export * from "./speedtest-tracker/speedtest-tracker-types"; +export * from "./umami/umami-types"; diff --git a/packages/integrations/src/umami/umami-integration.ts b/packages/integrations/src/umami/umami-integration.ts new file mode 100644 index 000000000..730cd5477 --- /dev/null +++ b/packages/integrations/src/umami/umami-integration.ts @@ -0,0 +1,450 @@ +import dayjs from "dayjs"; + +import { ResponseError } from "@homarr/common/server"; +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"; +import { TestConnectionError } from "../base/test-connection/test-connection-error"; +import type { TestingResult } from "../base/test-connection/test-connection-service"; +import type { + UmamiEventSeries, + UmamiMetricItem, + UmamiPageviewDataPoint, + UmamiPageviews, + UmamiStats, + UmamiVisitorStats, + UmamiWebsite, +} from "./umami-types"; +import { + umamiActiveVisitorsSchema, + umamiAuthResponseSchema, + umamiMetricItemSchema, + umamiPageviewDataPointSchema, + umamiPageviewsSchema, + umamiStatsSchema, + umamiWebsiteSchema, +} from "./umami-types"; + +// Umami event type ID for custom events (type 1 = page view, type 2 = custom event) +const UMAMI_CUSTOM_EVENT_TYPE = 2; + +const logger = createLogger({ module: "umami-integration" }); + +export class UmamiIntegration extends Integration { + protected async testingAsync(input: IntegrationTestingInput): Promise { + const authHeaders = await this.getAuthHeadersAsync(); + const url = this.url("/websites"); + const response = await input.fetchAsync(url, { + headers: authHeaders, + }); + + if (!response.ok) { + return TestConnectionError.StatusResult(response); + } + + return { success: true }; + } + + public async getWebsitesAsync(): Promise { + const authHeaders = await this.getAuthHeadersAsync(); + const url = this.url("/websites"); + const response = await fetchWithTrustedCertificatesAsync(url, { headers: authHeaders }); + if (!response.ok) throw new ResponseError(response); + const json = await response.json(); + // Response envelope differs by hosting: + // - Umami self-hosted returns the array directly: [website, ...] + // - Umami Cloud wraps it: { data: [website, ...] } + // Same divergence applies to the /events list responses below. + return Array.isArray(json) + ? umamiWebsiteSchema.array().parse(json) + : umamiWebsiteSchema.array().parse((json as { data: unknown }).data); + } + + // Server-side timeFrame resolution is intentional. Keeping `timeFrame` (a + // stable string like "7d") in the cache key means all users viewing the same + // dashboard share one Redis cache entry per (integration, website, timeFrame) + // for the 5-minute TTL. Client-supplied startAt/endAt would include a + // per-second `now` and fragment the cache one-per-user-per-second. + // + // Window bounds mirror Umami UI preset semantics, verified against the + // Umami Cloud dashboard for the same site and timeFrame: each "Last N" + // preset includes the in-progress period as one of N + 1 buckets, snapped + // to bucket boundaries. + // - "24h": current hour + 24 prior hours (25-bucket hour grid) + // - "7d": current day + 7 prior days ( 8-bucket day grid) + // - "30d": current day + 30 prior days (31-bucket day grid) + // - "today" / "month" / "lastMonth": calendar-aligned windows. + // + // Caveat: dayjs() uses the server's local timezone. Callers needing user-local + // boundaries (e.g. "today") may see off-by-one behaviour when the server + // runs in UTC but the user is in a different timezone. + private computeTimeRange(timeFrame: string): { startAt: number; endAt: number; unit: string } { + const startOfToday = dayjs().startOf("day"); + const endOfToday = dayjs().endOf("day"); + + switch (timeFrame) { + case "today": + return { startAt: startOfToday.valueOf(), endAt: endOfToday.valueOf(), unit: "hour" }; + case "7d": + return { + startAt: startOfToday.subtract(7, "day").valueOf(), + endAt: endOfToday.valueOf(), + unit: "day", + }; + case "30d": + return { + startAt: startOfToday.subtract(30, "day").valueOf(), + endAt: endOfToday.valueOf(), + unit: "day", + }; + case "month": + return { + startAt: dayjs().startOf("month").valueOf(), + endAt: dayjs().endOf("month").valueOf(), + unit: "day", + }; + case "lastMonth": { + const lastMonth = dayjs().subtract(1, "month"); + return { + startAt: lastMonth.startOf("month").valueOf(), + endAt: lastMonth.endOf("month").valueOf(), + unit: "day", + }; + } + case "24h": + default: + return { + startAt: dayjs().subtract(24, "hour").startOf("hour").valueOf(), + endAt: dayjs().endOf("hour").valueOf(), + unit: "hour", + }; + } + } + + public async getVisitorStatsAsync( + websiteId: string, + timeFrame = "24h", + eventName?: string, + ): Promise { + const authHeaders = await this.getAuthHeadersAsync(); + const website = await this.getWebsiteByIdAsync(websiteId, authHeaders); + + const { startAt, endAt, unit } = this.computeTimeRange(timeFrame); + + const [stats, pageviews, eventMetrics, eventTimeSeries] = await Promise.all([ + this.getWebsiteStatsAsync(website.id, startAt, endAt, authHeaders), + this.getWebsitePageviewsAsync(website.id, startAt, endAt, authHeaders, unit), + eventName ? this.getWebsiteEventMetricsAsync(website.id, startAt, endAt, authHeaders) : Promise.resolve(null), + eventName + ? this.getWebsiteEventTimeSeriesAsync(website.id, startAt, endAt, unit, eventName, authHeaders) + : Promise.resolve(null), + ]); + + const eventCount = eventName && eventMetrics ? (eventMetrics.find(({ x }) => x === eventName)?.y ?? 0) : undefined; + + const eventByTimestamp = new Map( + (eventTimeSeries ?? []).map(({ x, y }) => [this.parseUmamiDate(x), y]), + ); + const dataPoints = pageviews.sessions.map((point) => ({ + timestamp: point.x, + visitors: point.y, + ...(eventTimeSeries ? { events: eventByTimestamp.get(this.parseUmamiDate(point.x)) ?? 0 } : {}), + })); + + return { + domain: website.domain, + websiteName: website.name, + websiteId: website.id, + totalVisitors: stats.visitors, + totalPageviews: stats.pageviews, + totalVisits: stats.visits, + bounceRate: stats.visits > 0 ? Math.round((stats.bounces / stats.visits) * 100) : 0, + avgDuration: stats.visits > 0 ? Math.round(stats.totaltime / stats.visits) : 0, + dataPoints, + timeFrame, + eventCount, + }; + } + + public async getEventNamesAsync(websiteId: string): Promise { + const authHeaders = await this.getAuthHeadersAsync(); + const now = Date.now(); + const startAt = dayjs().subtract(30, "day").valueOf(); + const url = this.url(`/websites/${websiteId}/events`, { + startAt: startAt.toString(), + endAt: now.toString(), + pageSize: "100000", + }); + const response = await fetchWithTrustedCertificatesAsync(url, { headers: authHeaders }); + if (!response.ok) { + logger.warn("Failed to load event names", { status: response.status, websiteId }); + return []; + } + const json = await response.json(); + const rawArray: unknown[] = Array.isArray(json) + ? json + : json && typeof json === "object" && "data" in json && Array.isArray((json as { data: unknown }).data) + ? (json as { data: unknown[] }).data + : []; + const names = new Set(); + for (const item of rawArray) { + const record = item as Record; + if (typeof record.eventName === "string" && record.eventName) { + names.add(record.eventName); + } + } + return [...names].sort(); + } + + public async getActiveVisitorsAsync(websiteId: string): Promise { + const authHeaders = await this.getAuthHeadersAsync(); + const url = this.url(`/websites/${websiteId}/active`); + const response = await fetchWithTrustedCertificatesAsync(url, { headers: authHeaders }); + if (!response.ok) { + logger.warn("Failed to load active visitors", { status: response.status, websiteId }); + return 0; + } + return umamiActiveVisitorsSchema.parse(await response.json()).visitors; + } + + /** + * Umami self-hosted returns "YYYY-MM-DD HH:MM:SS"; Umami Cloud returns ISO 8601. + * Normalise both to epoch ms so timestamps from /pageviews and /events join. + */ + private parseUmamiDate(dateString: string): number { + return new Date(dateString.includes("T") ? dateString : `${dateString.replace(" ", "T")}Z`).getTime(); + } + + private async getWebsiteByIdAsync(websiteId: string, authHeaders: Record): Promise { + const url = this.url(`/websites/${websiteId}`); + const response = await fetchWithTrustedCertificatesAsync(url, { headers: authHeaders }); + if (!response.ok) throw new ResponseError(response); + return umamiWebsiteSchema.parse(await response.json()); + } + + public async getTopPagesAsync(websiteId: string, timeFrame: string, limit: number): Promise { + const authHeaders = await this.getAuthHeadersAsync(); + const { startAt, endAt } = this.computeTimeRange(timeFrame); + return this.getWebsiteMetricsAsync(websiteId, startAt, endAt, "path", authHeaders, limit); + } + + public async getTopReferrersAsync(websiteId: string, timeFrame: string, limit: number): Promise { + const authHeaders = await this.getAuthHeadersAsync(); + const { startAt, endAt } = this.computeTimeRange(timeFrame); + return this.getWebsiteMetricsAsync(websiteId, startAt, endAt, "referrer", authHeaders, limit); + } + + public async getMultiEventTimeSeriesAsync( + websiteId: string, + timeFrame: string, + eventNames: string[], + ): Promise { + const authHeaders = await this.getAuthHeadersAsync(); + const { startAt, endAt, unit } = this.computeTimeRange(timeFrame); + const results = await Promise.all( + eventNames.map(async (eventName) => { + const dataPoints = await this.getWebsiteEventTimeSeriesAsync( + websiteId, + startAt, + endAt, + unit, + eventName, + authHeaders, + ); + return { eventName, dataPoints: dataPoints ?? [] }; + }), + ); + return results; + } + + private async getWebsiteEventMetricsAsync( + websiteId: string, + startAt: number, + endAt: number, + authHeaders: Record, + ): Promise { + return await this.getWebsiteMetricsAsync(websiteId, startAt, endAt, "event", authHeaders); + } + + private async getWebsiteMetricsAsync( + websiteId: string, + startAt: number, + endAt: number, + type: "event" | "path" | "referrer", + authHeaders: Record, + limit?: number, + ): Promise { + const params: Record = { + startAt: startAt.toString(), + endAt: endAt.toString(), + type, + }; + if (limit !== undefined) params.limit = limit.toString(); + const url = this.url(`/websites/${websiteId}/metrics`, params); + const response = await fetchWithTrustedCertificatesAsync(url, { + headers: authHeaders, + }); + + if (!response.ok) { + logger.warn("Failed to load website metrics", { status: response.status, websiteId, type }); + return []; + } + + const json = await response.json(); + if (Array.isArray(json)) return umamiMetricItemSchema.array().parse(json); + logger.warn("Unexpected metrics response shape", { websiteId, type }); + return []; + } + + private async getWebsiteEventTimeSeriesAsync( + websiteId: string, + startAt: number, + endAt: number, + unit: string, + eventName: string, + authHeaders: Record, + ): Promise { + const url = this.url(`/websites/${websiteId}/events`, { + startAt: startAt.toString(), + endAt: endAt.toString(), + unit, + timezone: "UTC", + eventName, + pageSize: "100000", + }); + const response = await fetchWithTrustedCertificatesAsync(url, { + headers: authHeaders, + }); + + if (!response.ok) { + logger.warn("Failed to load event time series", { status: response.status, websiteId, eventName }); + return undefined; + } + + const json = await response.json(); + const rawArray: unknown[] = Array.isArray(json) + ? json + : json && typeof json === "object" && "data" in json && Array.isArray((json as { data: unknown }).data) + ? (json as { data: unknown[] }).data + : []; + + if (rawArray.length === 0) return undefined; + + const firstItem = rawArray[0] as Record; + + // Two response shapes coexist: + // - Umami self-hosted returns aggregated buckets: { x: timestamp, y: count } + // - Umami Cloud returns raw event records: { createdAt, eventName, eventType } + // We aggregate the raw Cloud records ourselves into the same bucket grid so + // the result joins cleanly with /pageviews timestamps. + + // Aggregated { x, y } format — parse directly (self-hosted instances) + if ("x" in firstItem && "y" in firstItem) { + const points = umamiPageviewDataPointSchema.array().parse(rawArray); + return points.length === 0 ? undefined : points; + } + + // Raw event records — aggregate by time bucket (Umami Cloud) + if ("createdAt" in firstItem) { + const pad = (number: number) => String(number).padStart(2, "0"); + // Format must match Umami's pageview x-axis timestamps: "YYYY-MM-DD HH:MM:SS" + const truncate = (isoDate: string): string => { + const date = new Date(isoDate); + const dateString = `${date.getUTCFullYear()}-${pad(date.getUTCMonth() + 1)}-${pad(date.getUTCDate())}`; + if (unit === "hour") { + return `${dateString} ${pad(date.getUTCHours())}:00:00`; + } + if (unit === "month") { + return `${date.getUTCFullYear()}-${pad(date.getUTCMonth() + 1)}-01 00:00:00`; + } + return `${dateString} 00:00:00`; + }; + + const buckets = new Map(); + for (const item of rawArray) { + const event = item as { eventType?: number; eventName?: string; createdAt: string }; + if (event.eventType !== UMAMI_CUSTOM_EVENT_TYPE || event.eventName !== eventName) continue; + const bucket = truncate(event.createdAt); + buckets.set(bucket, (buckets.get(bucket) ?? 0) + 1); + } + if (buckets.size === 0) return undefined; + return ( + Array.from(buckets.entries()) + .sort(([itemA], [itemB]) => itemA.localeCompare(itemB)) + // eslint-disable-next-line id-length + .map(([x, y]) => ({ x, y })) + ); + } + + return undefined; + } + + private async getWebsiteStatsAsync( + websiteId: string, + startAt: number, + endAt: number, + authHeaders: Record, + ): Promise { + const url = this.url(`/websites/${websiteId}/stats`, { + startAt: startAt.toString(), + endAt: endAt.toString(), + }); + const response = await fetchWithTrustedCertificatesAsync(url, { + headers: authHeaders, + }); + + if (!response.ok) { + throw new ResponseError(response); + } + + return umamiStatsSchema.parse(await response.json()); + } + + private async getWebsitePageviewsAsync( + websiteId: string, + startAt: number, + endAt: number, + authHeaders: Record, + unit = "hour", + ): Promise { + const url = this.url(`/websites/${websiteId}/pageviews`, { + startAt: startAt.toString(), + endAt: endAt.toString(), + unit, + timezone: "UTC", + }); + const response = await fetchWithTrustedCertificatesAsync(url, { + headers: authHeaders, + }); + + if (!response.ok) { + throw new ResponseError(response); + } + + return umamiPageviewsSchema.parse(await response.json()); + } + + private async getAuthHeadersAsync(): Promise> { + if (this.hasSecretValue("apiKey")) { + return { "x-umami-api-key": this.getSecretValue("apiKey") }; + } + const token = await this.getJwtTokenAsync(); + return { Authorization: `Bearer ${token}` }; + } + + private async getJwtTokenAsync(): Promise { + const url = this.url("/auth/login"); + const response = await fetchWithTrustedCertificatesAsync(url, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + username: this.getSecretValue("username"), + password: this.getSecretValue("password"), + }), + }); + if (!response.ok) throw new ResponseError(response); + return umamiAuthResponseSchema.parse(await response.json()).token; + } +} diff --git a/packages/integrations/src/umami/umami-types.ts b/packages/integrations/src/umami/umami-types.ts new file mode 100644 index 000000000..4e38b2a2c --- /dev/null +++ b/packages/integrations/src/umami/umami-types.ts @@ -0,0 +1,84 @@ +import { z } from "zod/v4"; + +export const umamiWebsiteSchema = z.object({ + id: z.string(), + name: z.string(), + domain: z.string(), +}); + +export type UmamiWebsite = z.infer; + +export const umamiAuthResponseSchema = z.object({ + token: z.string(), +}); + +export type UmamiAuthResponse = z.infer; + +export const umamiStatsSchema = z.object({ + pageviews: z.number(), + visitors: z.number(), + visits: z.number(), + bounces: z.number(), + totaltime: z.number(), +}); + +export type UmamiStats = z.infer; + +export const umamiPageviewDataPointSchema = z.object({ + x: z.string(), + y: z.number(), +}); + +export type UmamiPageviewDataPoint = z.infer; + +export const umamiPageviewsSchema = z.object({ + pageviews: z.array(umamiPageviewDataPointSchema), + sessions: z.array(umamiPageviewDataPointSchema), +}); + +export type UmamiPageviews = z.infer; + +export const umamiMetricItemSchema = z.object({ + // x can be null for direct traffic (no referrer) in the referrers endpoint + x: z + .string() + .nullable() + .transform((value) => value ?? ""), + y: z.number(), +}); + +export type UmamiMetricItem = z.infer; + +export const umamiEventRecordSchema = z.object({ + id: z.string(), + eventName: z.string(), + createdAt: z.string(), +}); + +export type UmamiEventRecord = z.infer; + +export const umamiActiveVisitorsSchema = z.object({ + visitors: z.number(), +}); + +export type UmamiActiveVisitors = z.infer; + +// Internal aggregated type returned by the integration — not a direct API response +export interface UmamiEventSeries { + eventName: string; + dataPoints: UmamiPageviewDataPoint[]; +} + +export interface UmamiVisitorStats { + domain: string; + websiteId: string; + websiteName: string; + totalVisitors: number; + totalPageviews: number; + totalVisits: number; + bounceRate: number; + avgDuration: number; + dataPoints: { timestamp: string; visitors: number; events?: number }[]; + timeFrame: string; + eventCount?: number; +} diff --git a/packages/request-handler/src/umami.ts b/packages/request-handler/src/umami.ts new file mode 100644 index 000000000..e943c568a --- /dev/null +++ b/packages/request-handler/src/umami.ts @@ -0,0 +1,84 @@ +import dayjs from "dayjs"; + +import { createIntegrationAsync } from "@homarr/integrations"; +import type { UmamiEventSeries, UmamiMetricItem, UmamiVisitorStats } from "@homarr/integrations/types"; + +import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; + +export const umamiRequestHandler = createCachedIntegrationRequestHandler< + UmamiVisitorStats, + "umami", + { websiteId: string; timeFrame: string; eventName?: string } +>({ + async requestAsync(integration, input) { + const integrationInstance = await createIntegrationAsync(integration); + return await integrationInstance.getVisitorStatsAsync(input.websiteId, input.timeFrame, input.eventName); + }, + cacheDuration: dayjs.duration(5, "minutes"), + queryKey: "umamiVisitorStats", +}); + +export const umamiEventNamesRequestHandler = createCachedIntegrationRequestHandler< + string[], + "umami", + { websiteId: string } +>({ + async requestAsync(integration, input) { + const integrationInstance = await createIntegrationAsync(integration); + return await integrationInstance.getEventNamesAsync(input.websiteId); + }, + cacheDuration: dayjs.duration(5, "minutes"), + queryKey: "umamiEventNames", +}); + +export const umamiTopPagesRequestHandler = createCachedIntegrationRequestHandler< + UmamiMetricItem[], + "umami", + { websiteId: string; timeFrame: string; limit: number } +>({ + async requestAsync(integration, input) { + const integrationInstance = await createIntegrationAsync(integration); + return await integrationInstance.getTopPagesAsync(input.websiteId, input.timeFrame, input.limit); + }, + cacheDuration: dayjs.duration(5, "minutes"), + queryKey: "umamiTopPages", +}); + +export const umamiTopReferrersRequestHandler = createCachedIntegrationRequestHandler< + UmamiMetricItem[], + "umami", + { websiteId: string; timeFrame: string; limit: number } +>({ + async requestAsync(integration, input) { + const integrationInstance = await createIntegrationAsync(integration); + return await integrationInstance.getTopReferrersAsync(input.websiteId, input.timeFrame, input.limit); + }, + cacheDuration: dayjs.duration(5, "minutes"), + queryKey: "umamiTopReferrers", +}); + +export const umamiMultiEventRequestHandler = createCachedIntegrationRequestHandler< + UmamiEventSeries[], + "umami", + { websiteId: string; timeFrame: string; eventNames: string[] } +>({ + async requestAsync(integration, input) { + const integrationInstance = await createIntegrationAsync(integration); + return await integrationInstance.getMultiEventTimeSeriesAsync(input.websiteId, input.timeFrame, input.eventNames); + }, + cacheDuration: dayjs.duration(5, "minutes"), + queryKey: "umamiMultiEvent", +}); + +export const umamiActiveVisitorsRequestHandler = createCachedIntegrationRequestHandler< + number, + "umami", + { websiteId: string } +>({ + async requestAsync(integration, input) { + const integrationInstance = await createIntegrationAsync(integration); + return await integrationInstance.getActiveVisitorsAsync(input.websiteId); + }, + cacheDuration: dayjs.duration(30, "seconds"), + queryKey: "umamiActiveVisitors", +}); diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json index 817abb3f8..cdde0511b 100644 --- a/packages/translation/src/lang/en.json +++ b/packages/translation/src/lang/en.json @@ -2096,6 +2096,104 @@ "internalServerError": "Failed to fetch Tracearr data" } }, + "umami": { + "name": "Umami Analytics", + "description": "Display visitor stats from your Umami analytics instance", + "visitors": "visitors", + "active": "active", + "pageviews": "pageviews", + "visits": "visits", + "bounceRate": "bounce", + "avgDuration": "avg", + "option": { + "websiteId": { + "label": "Website", + "description": "Select a website tracked by your Umami instance", + "placeholder": "Select a website", + "loading": "Loading websites...", + "noWebsites": "No websites found", + "configureIntegrationFirst": "Connect a Umami integration first" + }, + "timeFrame": { + "label": "Time frame", + "description": "The time period to display visitor data for", + "option": { + "today": "Today", + "24h": "Last 24 hours", + "7d": "Last 7 days", + "30d": "Last 30 days", + "month": "This month", + "lastMonth": "Last month" + } + }, + "eventName": { + "label": "Event", + "description": "Show total occurrences of a specific event", + "placeholder": "Select an event", + "loading": "Loading events...", + "noEvents": "No events found", + "none": "No event", + "configureWebsiteFirst": "Select a website first to load events" + }, + "chartType": { + "label": "Chart type", + "description": "Display style for the chart", + "option": { + "bar": "Bar chart", + "sparkline": "Sparkline" + } + }, + "chartStyle": { + "label": "Chart style", + "description": "How to display the event series in the chart", + "option": { + "grouped": "Side by side", + "overlay": "Overlay (subset)" + } + }, + "viewMode": { + "label": "View", + "description": "What to display in the widget body", + "option": { + "chart": "Chart", + "events": "Events chart", + "topPages": "Top pages", + "topReferrers": "Top referrers" + } + }, + "eventNames": { + "label": "Events", + "description": "Select one or more events to chart", + "placeholder": "Select events...", + "loading": "Loading events...", + "noEvents": "No events found", + "configureWebsiteFirst": "Select a website first to load events" + }, + "topCount": { + "label": "Items to show", + "description": "Number of top items to display (max 500)" + } + }, + "event": { + "occurrences": "events", + "label": "events" + }, + "topPages": { + "direct": "Direct" + }, + "topReferrers": { + "direct": "Direct" + }, + "events": { + "selectPrompt": "Select events to chart" + }, + "error": { + "noIntegration": "No Umami integration configured", + "noWebsite": "No website configured", + "noData": "No data available", + "internalServerError": "Failed to fetch Umami data" + } + }, "common": { "location": { "query": "City / Postal code", @@ -3668,6 +3766,9 @@ }, "tracearr": { "label": "Tracearr" + }, + "umamiActiveVisitors": { + "label": "Umami Active Visitors" } }, "interval": { diff --git a/packages/widgets/src/_inputs/index.ts b/packages/widgets/src/_inputs/index.ts index 8917664de..0c412b74e 100644 --- a/packages/widgets/src/_inputs/index.ts +++ b/packages/widgets/src/_inputs/index.ts @@ -12,6 +12,9 @@ import { WidgetSliderInput } from "./widget-slider-input"; import { WidgetSortedItemListInput } from "./widget-sortable-item-list-input"; import { WidgetSwitchInput } from "./widget-switch-input"; import { WidgetTextInput } from "./widget-text-input"; +import { WidgetUmamiEventInput } from "./widget-umami-event-input"; +import { WidgetUmamiEventNamesInput } from "./widget-umami-event-names-input"; +import { WidgetUmamiWebsiteInput } from "./widget-umami-website-input"; const mapping = { anchorNote: WidgetAnchorNoteInput, @@ -27,6 +30,9 @@ const mapping = { sortableItemList: WidgetSortedItemListInput, multiReleasesRepositories: WidgetMultiReleasesRepositoriesInput, dynamicSelect: WidgetDynamicSelectInput, + umamiEventName: WidgetUmamiEventInput, + umamiEventNames: WidgetUmamiEventNamesInput, + umamiWebsite: WidgetUmamiWebsiteInput, } satisfies Record; export const getInputForType = (type: TType) => { diff --git a/packages/widgets/src/_inputs/widget-umami-event-input.tsx b/packages/widgets/src/_inputs/widget-umami-event-input.tsx new file mode 100644 index 000000000..947822cef --- /dev/null +++ b/packages/widgets/src/_inputs/widget-umami-event-input.tsx @@ -0,0 +1,46 @@ +"use client"; + +import { Select, Text } from "@mantine/core"; + +import { clientApi } from "@homarr/api/client"; +import { useScopedI18n } from "@homarr/translation/client"; + +import type { CommonWidgetInputProps } from "./common"; +import { useWidgetInputTranslation } from "./common"; +import { useFormContext } from "./form"; + +export const WidgetUmamiEventInput = ({ property, kind }: CommonWidgetInputProps<"umamiEventName">) => { + const t = useScopedI18n("widget.umami.option.eventName"); + const tInput = useWidgetInputTranslation(kind, property); + const form = useFormContext(); + + const integrationIds = form.values.integrationIds; + const websiteId = form.values.options.websiteId as string | undefined; + + const { data: eventNames, isPending } = clientApi.widget.umami.getEventNames.useQuery( + { integrationId: integrationIds[0] ?? "", websiteId: websiteId ?? "" }, + { enabled: Boolean(integrationIds[0]) && Boolean(websiteId) }, + ); + + if (!websiteId || integrationIds.length === 0) { + return ( + + {t("configureWebsiteFirst")} + + ); + } + + const data = [{ value: "", label: t("none") }, ...(eventNames ?? [])]; + + return ( + ({ value: site.id, label: site.domain }))} + {...form.getInputProps(`options.${property}`)} + /> + ); +}; diff --git a/packages/widgets/src/index.tsx b/packages/widgets/src/index.tsx index a59f93f42..572f70aa4 100644 --- a/packages/widgets/src/index.tsx +++ b/packages/widgets/src/index.tsx @@ -46,6 +46,7 @@ import * as systemDisks from "./system-disks"; import * as systemResources from "./system-resources"; import * as timetable from "./timetable"; import * as tracearr from "./tracearr"; +import * as umami from "./umami"; import * as video from "./video"; import * as weather from "./weather"; @@ -91,6 +92,7 @@ export const widgetImports = { "immich-albumCarousel": immichAlbumCarousel, tracearr, speedtestTracker, + umami, } satisfies WidgetImportRecord; export type WidgetImports = typeof widgetImports; diff --git a/packages/widgets/src/options.ts b/packages/widgets/src/options.ts index bc207db83..75b758b3d 100644 --- a/packages/widgets/src/options.ts +++ b/packages/widgets/src/options.ts @@ -160,6 +160,21 @@ const optionsFactory = { defaultValue: "", withDescription: false, }), + umamiEventName: () => ({ + type: "umamiEventName" as const, + defaultValue: "", + withDescription: true, + }), + umamiEventNames: () => ({ + type: "umamiEventNames" as const, + defaultValue: [] as string[], + withDescription: true, + }), + umamiWebsite: () => ({ + type: "umamiWebsite" as const, + defaultValue: "", + withDescription: true, + }), sortableItemList: ( input: SortableItemListInput, ) => ({ diff --git a/packages/widgets/src/umami/component.tsx b/packages/widgets/src/umami/component.tsx new file mode 100644 index 000000000..2aec3b46e --- /dev/null +++ b/packages/widgets/src/umami/component.tsx @@ -0,0 +1,39 @@ +"use client"; + +import { Stack, Text } from "@mantine/core"; + +import { useScopedI18n } from "@homarr/translation/client"; + +import type { WidgetComponentProps } from "../definition"; +import { UmamiContent } from "./umami-content"; + +export default function UmamiWidget({ options, integrationIds }: WidgetComponentProps<"umami">) { + if (!options.websiteId || options.websiteId.trim() === "") { + return ; + } + + return ( + + ); +} + +function NoWebsiteConfigured() { + const t = useScopedI18n("widget.umami"); + return ( + + + {t("error.noWebsite")} + + + ); +} diff --git a/packages/widgets/src/umami/index.ts b/packages/widgets/src/umami/index.ts new file mode 100644 index 000000000..c55ecd261 --- /dev/null +++ b/packages/widgets/src/umami/index.ts @@ -0,0 +1,88 @@ +import { IconChartBar, IconServerOff } from "@tabler/icons-react"; +import { z } from "zod/v4"; + +import { createWidgetDefinition } from "../definition"; +import { optionsBuilder } from "../options"; + +export const timeFrameValues = ["today", "24h", "7d", "30d", "month", "lastMonth"] as const; + +export const { definition, componentLoader } = createWidgetDefinition("umami", { + icon: IconChartBar, + supportedIntegrations: ["umami"], + createOptions() { + return optionsBuilder.from( + (factory) => ({ + websiteId: factory.umamiWebsite(), + timeFrame: factory.select({ + options: timeFrameValues.map((value) => ({ + value, + label: (t) => t(`widget.umami.option.timeFrame.option.${value}`), + })), + defaultValue: "24h", + withDescription: true, + }), + viewMode: factory.select({ + options: [ + { value: "chart", label: (t) => t("widget.umami.option.viewMode.option.chart") }, + { value: "events", label: (t) => t("widget.umami.option.viewMode.option.events") }, + { value: "topPages", label: (t) => t("widget.umami.option.viewMode.option.topPages") }, + { value: "topReferrers", label: (t) => t("widget.umami.option.viewMode.option.topReferrers") }, + ] as const, + defaultValue: "chart", + withDescription: true, + }), + chartType: factory.select({ + options: [ + { value: "bar", label: (t) => t("widget.umami.option.chartType.option.bar") }, + { value: "sparkline", label: (t) => t("widget.umami.option.chartType.option.sparkline") }, + ] as const, + defaultValue: "bar", + withDescription: true, + }), + chartStyle: factory.select({ + options: [ + { value: "grouped", label: (t) => t("widget.umami.option.chartStyle.option.grouped") }, + { value: "overlay", label: (t) => t("widget.umami.option.chartStyle.option.overlay") }, + ] as const, + defaultValue: "grouped", + withDescription: true, + }), + eventName: factory.umamiEventName(), + eventNames: factory.umamiEventNames(), + topCount: factory.number({ + validate: z.number().int().min(1).max(500), + defaultValue: 5, + step: 1, + withDescription: true, + }), + }), + { + viewMode: { + shouldHide: (options) => !options.websiteId, + }, + chartType: { + shouldHide: (options) => + !options.websiteId || (options.viewMode !== "chart" && options.viewMode !== "events"), + }, + chartStyle: { + shouldHide: (options) => options.viewMode !== "chart" || !options.eventName || options.chartType !== "bar", + }, + eventName: { + shouldHide: (options) => !options.websiteId || options.viewMode !== "chart", + }, + eventNames: { + shouldHide: (options) => !options.websiteId || options.viewMode !== "events", + }, + topCount: { + shouldHide: (options) => !options.websiteId || options.viewMode === "chart" || options.viewMode === "events", + }, + }, + ); + }, + errors: { + INTERNAL_SERVER_ERROR: { + icon: IconServerOff, + message: (t) => t("widget.umami.error.internalServerError"), + }, + }, +}).withDynamicImport(() => import("./component")); diff --git a/packages/widgets/src/umami/umami-content.tsx b/packages/widgets/src/umami/umami-content.tsx new file mode 100644 index 000000000..da9494519 --- /dev/null +++ b/packages/widgets/src/umami/umami-content.tsx @@ -0,0 +1,220 @@ +"use client"; + +import { BarChart, LineChart } from "@mantine/charts"; +import { Box, Group, Stack, Text, useMantineColorScheme } from "@mantine/core"; + +import { clientApi } from "@homarr/api/client"; +import { formatDuration } from "@homarr/common"; +import type { UmamiVisitorStats } from "@homarr/integrations/types"; +import { useScopedI18n } from "@homarr/translation/client"; + +import { UmamiEventsContent } from "./umami-events-content"; +import { UmamiTopPagesContent, UmamiTopReferrersContent } from "./umami-top-list"; +import { formatTimeFrameLabel, formatXLabel } from "./umami-utils"; +import type { TimeFrame } from "./umami-utils"; + +interface UmamiContentProps { + integrationIds: string[]; + websiteId: string; + timeFrame: TimeFrame; + eventName?: string; + eventNames: string[]; + chartStyle: string; + chartType: string; + viewMode: string; + topCount: number; +} + +export function UmamiContent({ + integrationIds, + websiteId, + timeFrame, + eventName, + eventNames, + chartStyle, + chartType, + viewMode, + topCount, +}: UmamiContentProps) { + const t = useScopedI18n("widget.umami"); + const { colorScheme } = useMantineColorScheme(); + const tickColor = colorScheme === "dark" ? "#c1c2c5" : "#495057"; + + const [results] = clientApi.widget.umami.getVisitorStats.useSuspenseQuery({ + integrationIds, + websiteId, + timeFrame, + eventName, + }); + + const activeVisitorsInput = { integrationId: integrationIds[0] ?? "", websiteId }; + const [activeVisitors] = clientApi.widget.umami.getActiveVisitors.useSuspenseQuery(activeVisitorsInput); + const utils = clientApi.useUtils(); + clientApi.widget.umami.subscribeActiveVisitors.useSubscription(activeVisitorsInput, { + onData(count) { + utils.widget.umami.getActiveVisitors.setData(activeVisitorsInput, () => count); + }, + }); + + const { data: multiEventSeries } = clientApi.widget.umami.getMultiEventTimeSeries.useQuery( + { integrationId: integrationIds[0] ?? "", websiteId, timeFrame, eventNames: [...eventNames].sort() }, + { enabled: viewMode === "events" && eventNames.length > 0 }, + ); + + const multiEventTotal = multiEventSeries + ? multiEventSeries.flatMap(({ dataPoints }) => dataPoints).reduce((sum, { y }) => sum + y, 0) + : undefined; + + const firstResult = results[0]; + if (!firstResult) { + return ( + + + {t("error.noData")} + + + ); + } + + const { visitorStats } = firstResult; + + const chartData = visitorStats.dataPoints.map((point: UmamiVisitorStats["dataPoints"][number]) => ({ + label: formatXLabel(point.timestamp, timeFrame), + visitors: point.visitors, + ...(point.events !== undefined ? { events: point.events } : {}), + })); + + const hasEventSeries = visitorStats.dataPoints.some(({ events }) => events !== undefined); + const series = [ + { name: "visitors", color: "blue.5" }, + ...(hasEventSeries ? [{ name: "events", color: "orange.5" }] : []), + ]; + + const isOverlay = chartStyle === "overlay" && hasEventSeries; + const overlayBarSize = 16; + + return ( + + + + {visitorStats.domain} ({formatTimeFrameLabel(timeFrame, t)}) + + {(eventName && viewMode === "chart" && visitorStats.eventCount !== undefined) || + (viewMode === "events" && multiEventTotal !== undefined) ? ( + + + + {viewMode === "events" + ? (multiEventTotal ?? 0).toLocaleString() + : (visitorStats.eventCount ?? 0).toLocaleString()} + + + {t("event.label")} + + + + / + + + + {visitorStats.totalVisitors.toLocaleString()} + + + {t("visitors")} + + + + ) : ( + + + {visitorStats.totalVisitors.toLocaleString()} + + + {t("visitors")} + + + )} + + + + ● {activeVisitors.toLocaleString()} {t("active")} + + + {visitorStats.totalPageviews.toLocaleString()} {t("pageviews")} + + + {visitorStats.totalVisits.toLocaleString()} {t("visits")} + + + {visitorStats.bounceRate}% {t("bounceRate")} + + + {formatDuration(visitorStats.avgDuration * 1000)} {t("avgDuration")} + + + + {viewMode === "events" ? ( + + ) : viewMode === "topPages" ? ( + + ) : viewMode === "topReferrers" ? ( + + ) : chartType === "sparkline" ? ( + + ) : ( + + )} + + + ); +} diff --git a/packages/widgets/src/umami/umami-events-content.tsx b/packages/widgets/src/umami/umami-events-content.tsx new file mode 100644 index 000000000..0962d3681 --- /dev/null +++ b/packages/widgets/src/umami/umami-events-content.tsx @@ -0,0 +1,138 @@ +"use client"; + +import { BarChart, LineChart } from "@mantine/charts"; +import { Box, Group, Stack, Text, useMantineColorScheme } from "@mantine/core"; + +import { clientApi } from "@homarr/api/client"; +import type { UmamiEventSeries } from "@homarr/integrations/types"; +import { useScopedI18n } from "@homarr/translation/client"; + +import { EVENT_COLORS, formatXLabel } from "./umami-utils"; + +interface UmamiEventsContentProps { + integrationIds: string[]; + websiteId: string; + timeFrame: string; + eventNames: string[]; + chartType: string; +} + +export function UmamiEventsContent({ + integrationIds, + websiteId, + timeFrame, + eventNames, + chartType, +}: UmamiEventsContentProps) { + const t = useScopedI18n("widget.umami"); + const { colorScheme } = useMantineColorScheme(); + const tickColor = colorScheme === "dark" ? "#c1c2c5" : "#495057"; + + const [series] = clientApi.widget.umami.getMultiEventTimeSeries.useSuspenseQuery({ + integrationId: integrationIds[0] ?? "", + websiteId, + timeFrame, + eventNames: [...eventNames].sort(), + }); + + if (eventNames.length === 0) { + return ( + + + {t("events.selectPrompt")} + + + ); + } + + // Collect all unique timestamps across all series + const allTimestamps = Array.from( + new Set(series.flatMap(({ dataPoints }) => dataPoints.map(({ x: xPoint }) => xPoint))), + ).sort(); + + // Build per-event lookup by timestamp string + const byEvent = new Map( + series.map((serie: UmamiEventSeries) => [ + serie.eventName, + new Map(serie.dataPoints.map((point) => [point.x, point.y])), + ]), + ); + + const chartData = allTimestamps.map((timestamp) => { + const row: Record = { label: formatXLabel(timestamp, timeFrame) }; + for (const serie of series) { + row[serie.eventName] = byEvent.get(serie.eventName)?.get(timestamp) ?? 0; + } + return row; + }); + + const chartSeries = series.map((serie: UmamiEventSeries, index: number) => ({ + name: serie.eventName, + color: EVENT_COLORS[index % EVENT_COLORS.length] ?? "blue.5", + })); + + return ( + + {chartSeries.length > 1 && ( + + {chartSeries.map((serie) => ( + + + + {serie.name} + + + ))} + + )} + + {chartType === "sparkline" ? ( + + ) : ( + + )} + + + ); +} diff --git a/packages/widgets/src/umami/umami-top-list.tsx b/packages/widgets/src/umami/umami-top-list.tsx new file mode 100644 index 000000000..1d9a87988 --- /dev/null +++ b/packages/widgets/src/umami/umami-top-list.tsx @@ -0,0 +1,77 @@ +"use client"; + +import { Group, ScrollArea, Stack, Text } from "@mantine/core"; + +import { clientApi } from "@homarr/api/client"; +import type { UmamiMetricItem } from "@homarr/integrations/types"; +import { useScopedI18n } from "@homarr/translation/client"; + +interface UmamiTopListProps { + integrationIds: string[]; + websiteId: string; + timeFrame: string; + limit: number; +} + +export function UmamiTopPagesContent({ integrationIds, websiteId, timeFrame, limit }: UmamiTopListProps) { + const t = useScopedI18n("widget.umami"); + const [data] = clientApi.widget.umami.getTopPages.useSuspenseQuery({ + integrationId: integrationIds[0] ?? "", + websiteId, + timeFrame, + limit, + }); + return ; +} + +export function UmamiTopReferrersContent({ integrationIds, websiteId, timeFrame, limit }: UmamiTopListProps) { + const t = useScopedI18n("widget.umami"); + const [data] = clientApi.widget.umami.getTopReferrers.useSuspenseQuery({ + integrationId: integrationIds[0] ?? "", + websiteId, + timeFrame, + limit, + }); + return ( + + ); +} + +function UmamiTopList({ + items, + heading, + emptyLabel, +}: { + items: UmamiMetricItem[]; + heading: string; + emptyLabel: string; +}) { + return ( + + + {heading} + + + + {items.map((item, i) => ( + + + {i + 1}. + + + {item.x || emptyLabel} + + + {item.y.toLocaleString()} + + + ))} + + + + ); +} diff --git a/packages/widgets/src/umami/umami-utils.ts b/packages/widgets/src/umami/umami-utils.ts new file mode 100644 index 000000000..c88f28378 --- /dev/null +++ b/packages/widgets/src/umami/umami-utils.ts @@ -0,0 +1,33 @@ +import type { useScopedI18n } from "@homarr/translation/client"; + +import { timeFrameValues } from "./index"; + +export const EVENT_COLORS = ["blue.5", "orange.5", "green.5", "red.5", "violet.5", "teal.5", "yellow.5", "pink.5"]; + +export function formatXLabel(timestamp: string, timeFrame: string): string { + const date = new Date(timestamp); + switch (timeFrame) { + case "today": + case "24h": + return `${date.getHours().toString().padStart(2, "0")}:00`; + case "7d": + case "30d": + case "month": + case "lastMonth": + return date.toLocaleDateString(undefined, { month: "short", day: "numeric" }); + default: + return timestamp; + } +} + +export type TimeFrame = (typeof timeFrameValues)[number]; + +export function formatTimeFrameLabel( + timeFrame: TimeFrame, + t: ReturnType>, +): string { + if (timeFrameValues.includes(timeFrame)) { + return t(`option.timeFrame.option.${timeFrame}`); + } + return timeFrame; +}