From f413c446e38279086c2626eb812ad30688185e68 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 09:15:38 +0200 Subject: [PATCH 01/14] chore(deps): update github-actions (#30089) --- .github/workflows/build-mobile.yml | 4 ++-- .github/workflows/check-openapi.yml | 2 +- .github/workflows/close-duplicates.yml | 2 +- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/prepare-release.yml | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-mobile.yml b/.github/workflows/build-mobile.yml index 39a1a930779137..1c74b34c558753 100644 --- a/.github/workflows/build-mobile.yml +++ b/.github/workflows/build-mobile.yml @@ -106,7 +106,7 @@ jobs: working-directory: ./mobile run: printf "%s" $KEY_JKS | base64 -d > android/key.jks - - uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0 + - uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 with: distribution: 'zulu' java-version: '17' @@ -241,7 +241,7 @@ jobs: run: flutter build ios --config-only --no-codesign - name: Setup Ruby - uses: ruby/setup-ruby@d45b1a4e94b71acab930e56e79c6aa188764e7f9 # v1.316.0 + uses: ruby/setup-ruby@003a5c4d8d6321bd302e38f6f0ec593f77f06600 # v1.319.0 with: ruby-version: '3.3' bundler-cache: true diff --git a/.github/workflows/check-openapi.yml b/.github/workflows/check-openapi.yml index 85fb3ed38c0804..7df2bfd6ab6e4b 100644 --- a/.github/workflows/check-openapi.yml +++ b/.github/workflows/check-openapi.yml @@ -25,7 +25,7 @@ jobs: persist-credentials: false - name: Check for breaking API changes - uses: oasdiff/oasdiff-action/breaking@291b6594ce617f22f49af5898ee038ed0b46e8fe # v0.1.5 + uses: oasdiff/oasdiff-action/breaking@024f6c399f9a21ada1addb0f9a36ce1bfac995f1 # v0.1.6 with: base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json revision: open-api/immich-openapi-specs.json diff --git a/.github/workflows/close-duplicates.yml b/.github/workflows/close-duplicates.yml index 157ff001b476ab..cf932dac8d5200 100644 --- a/.github/workflows/close-duplicates.yml +++ b/.github/workflows/close-duplicates.yml @@ -39,7 +39,7 @@ jobs: needs: [get_body, should_run] if: ${{ needs.should_run.outputs.should_run == 'true' }} container: - image: ghcr.io/immich-app/mdq:main@sha256:e3fe456f346b02c2f479462100763aee3ba8245768dfeac3c4262acb068e5c62 + image: ghcr.io/immich-app/mdq:main@sha256:d603f5618c1fc9f2b4d506ffed54ba6bcfec83d56b893b5fa8b3f899a402fa0a outputs: checked: ${{ steps.get_checkbox.outputs.checked }} steps: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index e36bbda495bac9..9bc8b913ca4d36 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -58,7 +58,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + uses: github/codeql-action/init@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -71,7 +71,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + uses: github/codeql-action/autobuild@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1 # â„šī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -84,6 +84,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + uses: github/codeql-action/analyze@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1 with: category: '/language:${{matrix.language}}' diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index bd580b72ade2f5..976ba9906a30db 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -152,7 +152,7 @@ jobs: github-token: ${{ steps.generate-token.outputs.token }} - name: Create draft release - uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1 + uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2 with: draft: true prerelease: ${{ needs.bump_version.outputs.rc }} From 998fd5f6a88c9db7ed864881e24120ab87218aa5 Mon Sep 17 00:00:00 2001 From: Ijas Date: Wed, 22 Jul 2026 13:49:19 +0530 Subject: [PATCH 02/14] fix(mobile): send date-only value for memories query param (#30049) * fix(ios-widget): send date-only value for memories query param Server v3.0.3 (immich-app#29907) validates the /memories `for` query param with a date-only schema (YYYY-MM-DD). The iOS widget still sent a full ISO 8601 datetime, so every request 400s and the widget shows "Unable to connect to your Immich instance" on any server >= v3.0.3. Build the value from the local calendar day, matching what the web client (memory-manager.svelte.ts) and Android already send, instead of a UTC datetime. Also fix a related bug in the timeline provider: fetchMemory was called with a bare try inside a Task, so a failure silently dropped the error and never called completion, leaving the widget in a stale state instead of showing an error card. Wrap it in a guard/try?, same pattern already used in getSnapshot. Fixes immich-app#30044 Signed-off-by: ajuijas <189517297+ajuijas@users.noreply.github.com> * address review: drop unrelated widget change and comment Signed-off-by: ajuijas <189517297+ajuijas@users.noreply.github.com> --------- Signed-off-by: ajuijas <189517297+ajuijas@users.noreply.github.com> Co-authored-by: ajuijas <189517297+ajuijas@users.noreply.github.com> Co-authored-by: Santo Shakil --- mobile/ios/WidgetExtension/ImmichAPI.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mobile/ios/WidgetExtension/ImmichAPI.swift b/mobile/ios/WidgetExtension/ImmichAPI.swift index c5d7971abac6bb..f5ee578dade4b9 100644 --- a/mobile/ios/WidgetExtension/ImmichAPI.swift +++ b/mobile/ios/WidgetExtension/ImmichAPI.swift @@ -203,7 +203,10 @@ class ImmichAPI { func fetchMemory(for date: Date) async throws -> [MemoryResult] { // get URL - let memoryParams = [URLQueryItem(name: "for", value: date.ISO8601Format())] + let localDay = date.formatted( + Date.ISO8601FormatStyle(timeZone: .current).year().month().day().dateSeparator(.dash) + ) + let memoryParams = [URLQueryItem(name: "for", value: localDay)] guard let searchURL = buildRequestURL( serverConfig: serverConfig, From 0f5bf6071fd908f25f6037f949a77403b68c07dc Mon Sep 17 00:00:00 2001 From: Ben Beckford Date: Wed, 22 Jul 2026 01:48:31 -0700 Subject: [PATCH 03/14] chore(server): test assetDateFilter workflow method (#30110) --- .../workflow/workflow-core-plugin.spec.ts | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/server/test/medium/specs/workflow/workflow-core-plugin.spec.ts b/server/test/medium/specs/workflow/workflow-core-plugin.spec.ts index d8830e91e6f5d9..1ad40ecca764cb 100644 --- a/server/test/medium/specs/workflow/workflow-core-plugin.spec.ts +++ b/server/test/medium/specs/workflow/workflow-core-plugin.spec.ts @@ -433,9 +433,10 @@ describe('core plugin', () => { describe('assetDateFilter', () => { it('should favorite assets created during the first 7 days of a specific year and month', async () => { const { user } = await ctx.newUser(); - const [{ asset: asset1 }, { asset: asset2 }] = await Promise.all([ + const [{ asset: asset1 }, { asset: asset2 }, { asset: asset3 }] = await Promise.all([ ctx.newAsset({ ownerId: user.id, localDateTime: new Date('2000-04-01') }), ctx.newAsset({ ownerId: user.id, localDateTime: new Date('2000-04-07T23:59:59Z') }), + ctx.newAsset({ ownerId: user.id, localDateTime: new Date('2000-04-08T00:00:00Z') }), ]); const workflow = await createWorkflow({ @@ -461,6 +462,46 @@ describe('core plugin', () => { await ctx.sut.handleAssetTrigger({ workflowId: workflow.id, assetId: asset2.id }); await expect(ctx.get(AssetRepository).getById(asset2.id)).resolves.toMatchObject({ isFavorite: true }); + + await ctx.sut.handleAssetTrigger({ workflowId: workflow.id, assetId: asset3.id }); + await expect(ctx.get(AssetRepository).getById(asset3.id)).resolves.toMatchObject({ isFavorite: false }); + }); + + it('should match recurring dates regardless of the year', async () => { + const { user } = await ctx.newUser(); + const [{ asset: asset1 }, { asset: asset2 }, { asset: asset3 }] = await Promise.all([ + ctx.newAsset({ ownerId: user.id, localDateTime: new Date('2026-03-01') }), + ctx.newAsset({ ownerId: user.id, localDateTime: new Date('1998-12-21') }), + ctx.newAsset({ ownerId: user.id, localDateTime: new Date('2000-04-08T00:00:00Z') }), + ]); + await ctx.newAsset({ ownerId: user.id, localDateTime: new Date('2010-06-15') }); + + const workflow = await createWorkflow({ + ownerId: user.id, + trigger: WorkflowTrigger.AssetCreate, + steps: [ + { + method: 'immich-plugin-core#assetDateFilter', + config: { + startDate: { day: 12, month: 12, year: 2000 }, + endDate: { day: 30, month: 3, year: 2001 }, + recurring: true, + }, + }, + { + method: 'immich-plugin-core#assetFavorite', + }, + ], + }); + + await ctx.sut.handleAssetTrigger({ workflowId: workflow.id, assetId: asset1.id }); + await expect(ctx.get(AssetRepository).getById(asset1.id)).resolves.toMatchObject({ isFavorite: true }); + + await ctx.sut.handleAssetTrigger({ workflowId: workflow.id, assetId: asset2.id }); + await expect(ctx.get(AssetRepository).getById(asset2.id)).resolves.toMatchObject({ isFavorite: true }); + + await ctx.sut.handleAssetTrigger({ workflowId: workflow.id, assetId: asset3.id }); + await expect(ctx.get(AssetRepository).getById(asset3.id)).resolves.toMatchObject({ isFavorite: false }); }); }); From 99370b2ac452ffd5507d1fb675931e9af81260f7 Mon Sep 17 00:00:00 2001 From: Yaros Date: Wed, 22 Jul 2026 10:54:26 +0200 Subject: [PATCH 04/14] fix: admin user details responsive layout (#30114) --- web/src/lib/components/CalendarHeatmap.svelte | 52 ++++++++++--------- .../routes/admin/users/[id]/+layout.svelte | 2 +- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/web/src/lib/components/CalendarHeatmap.svelte b/web/src/lib/components/CalendarHeatmap.svelte index 477e796243683d..f2882c63b790df 100644 --- a/web/src/lib/components/CalendarHeatmap.svelte +++ b/web/src/lib/components/CalendarHeatmap.svelte @@ -55,34 +55,36 @@ {/each} --> -
-
- {#if Info.getStartOfWeek({ locale: $locale }) === 7} -
- {/if} -
{weekdays[0]}
-
{weekdays[1]}
-
{weekdays[2]}
- {#if Info.getStartOfWeek({ locale: $locale }) === 1} -
{weekdays[3]}
- {/if} -
+
+
+
+ {#if Info.getStartOfWeek({ locale: $locale }) === 7} +
+ {/if} +
{weekdays[0]}
+
{weekdays[1]}
+
{weekdays[2]}
+ {#if Info.getStartOfWeek({ locale: $locale }) === 1} +
{weekdays[3]}
+ {/if} +
- {#each data.series as day, idx (day.date)} - {@const date = DateTime.fromISO(day.date, { zone: 'utc' }).toLocaleString( - { month: 'short', day: 'numeric' }, - { locale: $locale }, - )} -
- {/each} + {#each data.series as day, idx (day.date)} + {@const date = DateTime.fromISO(day.date, { zone: 'utc' }).toLocaleString( + { month: 'short', day: 'numeric' }, + { locale: $locale }, + )} +
+ {/each} +
-
+
{$t('less')} diff --git a/web/src/routes/admin/users/[id]/+layout.svelte b/web/src/routes/admin/users/[id]/+layout.svelte index 8e5a8c90b0efe5..dc49e5f4211496 100644 --- a/web/src/routes/admin/users/[id]/+layout.svelte +++ b/web/src/routes/admin/users/[id]/+layout.svelte @@ -213,7 +213,7 @@ -
+
{$t('uploads')} From ba9ec7c80ed097be26a5141cafdf8d55632104d0 Mon Sep 17 00:00:00 2001 From: Yaros Date: Wed, 22 Jul 2026 10:58:38 +0200 Subject: [PATCH 05/14] fix: backup delay translation key parsing (#30112) --- .../backup_settings/drift_backup_settings.dart | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/mobile/lib/widgets/settings/backup_settings/drift_backup_settings.dart b/mobile/lib/widgets/settings/backup_settings/drift_backup_settings.dart index e5debb43feb70e..44a1a1d7a9db8b 100644 --- a/mobile/lib/widgets/settings/backup_settings/drift_backup_settings.dart +++ b/mobile/lib/widgets/settings/backup_settings/drift_backup_settings.dart @@ -1,6 +1,5 @@ import 'dart:async'; -import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; @@ -10,6 +9,7 @@ import 'package:immich_mobile/domain/services/sync_linked_album.service.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/backup/backup_album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; @@ -255,11 +255,11 @@ class _BackupDelaySlider extends ConsumerWidget { _ => 600, }; - static String formatBackupDelaySliderValue(int v) => switch (v) { - 0 => 'setting_notifications_notify_seconds'.tr(namedArgs: {'count': '5'}), - 1 => 'setting_notifications_notify_seconds'.tr(namedArgs: {'count': '30'}), - 2 => 'setting_notifications_notify_minutes'.tr(namedArgs: {'count': '2'}), - _ => 'setting_notifications_notify_minutes'.tr(namedArgs: {'count': '10'}), + static String formatBackupDelaySliderValue(BuildContext context, int v) => switch (v) { + 0 => context.t.setting_notifications_notify_seconds(count: 5), + 1 => context.t.setting_notifications_notify_seconds(count: 30), + 2 => context.t.setting_notifications_notify_minutes(count: 2), + _ => context.t.setting_notifications_notify_minutes(count: 10), }; @override @@ -272,8 +272,8 @@ class _BackupDelaySlider extends ConsumerWidget { Padding( padding: const EdgeInsets.only(left: 24.0, top: 8.0), child: Text( - 'backup_controller_page_background_delay'.tr( - namedArgs: {'duration': formatBackupDelaySliderValue(currentValue)}, + context.t.backup_controller_page_background_delay( + duration: formatBackupDelaySliderValue(context, currentValue), ), style: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500), ), @@ -291,7 +291,7 @@ class _BackupDelaySlider extends ConsumerWidget { max: 3.0, min: 0.0, divisions: 3, - label: formatBackupDelaySliderValue(currentValue), + label: formatBackupDelaySliderValue(context, currentValue), ), ], ); From 75053ebfbaa79414387b9cd2a1f9319bdc8880fc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 10:59:04 +0200 Subject: [PATCH 06/14] chore(deps): update terraform cloudflare to v4.52.8 (#29671) --- .../docs-release/.terraform.lock.hcl | 58 +++++++++---------- .../modules/cloudflare/docs-release/config.tf | 2 +- .../cloudflare/docs/.terraform.lock.hcl | 58 +++++++++---------- deployment/modules/cloudflare/docs/config.tf | 2 +- 4 files changed, 58 insertions(+), 62 deletions(-) diff --git a/deployment/modules/cloudflare/docs-release/.terraform.lock.hcl b/deployment/modules/cloudflare/docs-release/.terraform.lock.hcl index 7fdd96847ca963..049d3c0fd9bd00 100644 --- a/deployment/modules/cloudflare/docs-release/.terraform.lock.hcl +++ b/deployment/modules/cloudflare/docs-release/.terraform.lock.hcl @@ -2,37 +2,35 @@ # Manual edits may be lost in future updates. provider "registry.opentofu.org/cloudflare/cloudflare" { - version = "4.52.7" - constraints = "4.52.7" + version = "4.52.8" + constraints = "4.52.8" hashes = [ - "h1:+O72J3QYiZtYmYYZM/Eh0f4NNfl1BvjX1eju43qTQsQ=", - "h1:0oqjYIPXcXh7XiDiKI085cHDYQQ5mh8kDl9dmBtvtog=", - "h1:4b4ESb87MGv5bnadgYe7sK5rEkKMZhbkQcwPubQTsR4=", - "h1:6mTr3eA1Ddb348lLmJuyvn98z4KF+ejqaUEJ76D1rzQ=", - "h1:9/3YH+9k9HqsvFtbmBf7SO2+xqZeZrXNKzLkjNuhUEA=", - "h1:Jcq4tBWgyH4/2JsojNBSRaN0mcItVMchO+lynonrlqc=", - "h1:Y4Vv/2RdP0Q+uxqhOxzOdKxuuEMjXPDcU0vPc5bCQzI=", - "h1:a0gW8FBKsbP9Fi0HEDoy49WIbEWVHk9+BR4/iwuBdDQ=", - "h1:gElv6iqJtg8OKN77gbw+MjrkrQmJHPkkMEi1J+0xkpU=", - "h1:oslXUugD/NQ+duJgT4BhKQyfGbuFOANknMvR73fiOeM=", - "h1:pPItIWii5oymR+geZB219ROSPuSODPLTlM4S/u8xLvM=", - "h1:u67GWw8GwD9NDlDzp9Y5VRnSQGcCrE8rSpkGPaBpDl0=", - "h1:uUUa9dY0XQOycI8pxg16PFFtL0WCTi9uEJz8trTQ7pU=", - "h1:y3rV8KF2q6GEMANNlf5EkKJurlfbKlIKpjGcdxoy7pQ=", - "zh:0c904ce31a4c6c4a5b3bf7ff1560e77c0cc7e2450c8553ded8e8c90398e1418b", - "zh:36183d310c36373fe4cb936b83c595c6fd3b0a94bc7827f28e5789ccbf59752e", - "zh:556a568a6f0235e8f41647de9e4d3a1e7b1d6502df8b19b54ec441f1c653ea10", - "zh:633ebbd5b0245e75e500ef9be4d9e62288f97e8da3baaa51323892a786d90285", - "zh:6acfe60cf52a65ba8f044f748548d2119e7f4fd7f8ebcb14698960d87c68f529", + "h1:2uxVaMaGPDJljXqYzuD6xtyfOKdNudtGIgawRTkFJIs=", + "h1:34HL1GDpqYdn2iEC+85pCi+NddZf30qH3fJ8y3P6CAM=", + "h1:4Z+uD+YY/7HrqdypM7DyUK+RtWepoLBc9fqtRQYZx4I=", + "h1:8Ss6wlJL2BHvkIZU1iaHvyUntqNhPFZ0fl6mjsxhctc=", + "h1:AYeWWMTWaNg01N3hivxr/6YlG41j1RietMnft9vLjUI=", + "h1:BGRNOzo8NUgbx5RwpWkWmr38f/s3txb7mzQhqfM5blI=", + "h1:EEXRMdC8bPlpLuOU8u+dE8mFJe3BpquFdH6vXcKseY4=", + "h1:ENp29e6O6OnaHJlbtmRlyjlAmm3Wt4mGctWUpBY3d0g=", + "h1:IAtBAbGVe1npj/n1NvCIZkSYGl7qaPYT+FnVdZ2CZ00=", + "h1:QRGGKngrxLORr4CImKXLDDJL5s67zUNBDAgk6FIIFNk=", + "h1:bNql6cC+MfDbaJK3q7a8C1m3A21o7e5mrjBIGtk0RdI=", + "h1:gmNv+PEOkYDdu+c15bfpcoAE3EI7pgmov9RMf3GZLsI=", + "h1:yieZ7NWRYQcLYHUevzDDrrmDkoQICM+3ZeX4i/mw9ME=", + "zh:08b305329a680a9213b2d8e642fbce7e4d97a524b1d2cef59e190ba9d678c477", + "zh:47975bd711ee18a46e589822171fa87474a552b332bfc8dea8fd1a64504eed8d", + "zh:5640d0d226bbafff3395542456c29feb942ca9c55ac01b4245a34c0590a33363", + "zh:5b0ad839fafba938c60a95d6b4a865843643591e97573ed2b9c4af3754064f74", "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", - "zh:904acc31ebb9d6ef68c792074b30532ee61bf515f19e0a3c75b46f126cca1f13", - "zh:a1d0a81246afc8750286d3f6fe7a8fbe6460dd2662407b28dbfbabb612e5fa9d", - "zh:a41a36fe253fc365fe2b7ffc749624688b2693b4634862fda161179ab100029f", - "zh:a7ef269e77ffa8715c8945a2c14322c7ff159ea44c15f62505f3cbb2cae3b32d", - "zh:b01aa3bed30610633b762df64332b26f8844a68c3960cebcb30f04918efc67fe", - "zh:b069cc2cd18cae10757df3ae030508eac8d55de7e49eda7a5e3e11f2f7fe6455", - "zh:b2d2c6313729ebb7465dceece374049e2d08bda34473901be9ff46a8836d42b2", - "zh:db0e114edaf4bc2f3d4769958807c83022bfbc619a00bdf4c4bd17faa4ab2d8b", - "zh:ecc0aa8b9044f664fd2aaf8fa992d976578f78478980555b4b8f6148e8d1a5fe", + "zh:93a9bc1139f5c02a44fdbf51fb2ce0891e2a42033b66febb928ccf72e870408f", + "zh:977f75cdf365686aa16ae02dcc0fc1769bae6f86be6e165393756c940bfb4af0", + "zh:9afcda2660b3dc6ee6329acea532a719225bd1f6cf46f695feb2d77160847c1b", + "zh:9e3da67b1b05b03d1f0c18b8677edf3797815b5dd49629f10eb2f457dccbed30", + "zh:b8d7da230f5266367c1b6b1cf31aa39087e231a87d55b71bb9d5c854ca1fcfe6", + "zh:cc96e7cd7350456b7a11a53d1c76c73a542de8359f9750f637ff865e8e77be91", + "zh:da1f58d067def243047bb7178cb197e2b9c3a791a9eb380ad92b73290615ae29", + "zh:ed5f3e1f59a338bcdce0a2537a8a2f299cfe18c168626799b2a9c97af3d8d3cb", + "zh:f16cc31f73a58a26ffe2d93223eddb7c340623a4e2ef38c6091766fd611e3e11", ] } diff --git a/deployment/modules/cloudflare/docs-release/config.tf b/deployment/modules/cloudflare/docs-release/config.tf index 7c59cdd2e3af75..ea1a441a4b087c 100644 --- a/deployment/modules/cloudflare/docs-release/config.tf +++ b/deployment/modules/cloudflare/docs-release/config.tf @@ -5,7 +5,7 @@ terraform { required_providers { cloudflare = { source = "cloudflare/cloudflare" - version = "4.52.7" + version = "4.52.8" } } } diff --git a/deployment/modules/cloudflare/docs/.terraform.lock.hcl b/deployment/modules/cloudflare/docs/.terraform.lock.hcl index 7fdd96847ca963..049d3c0fd9bd00 100644 --- a/deployment/modules/cloudflare/docs/.terraform.lock.hcl +++ b/deployment/modules/cloudflare/docs/.terraform.lock.hcl @@ -2,37 +2,35 @@ # Manual edits may be lost in future updates. provider "registry.opentofu.org/cloudflare/cloudflare" { - version = "4.52.7" - constraints = "4.52.7" + version = "4.52.8" + constraints = "4.52.8" hashes = [ - "h1:+O72J3QYiZtYmYYZM/Eh0f4NNfl1BvjX1eju43qTQsQ=", - "h1:0oqjYIPXcXh7XiDiKI085cHDYQQ5mh8kDl9dmBtvtog=", - "h1:4b4ESb87MGv5bnadgYe7sK5rEkKMZhbkQcwPubQTsR4=", - "h1:6mTr3eA1Ddb348lLmJuyvn98z4KF+ejqaUEJ76D1rzQ=", - "h1:9/3YH+9k9HqsvFtbmBf7SO2+xqZeZrXNKzLkjNuhUEA=", - "h1:Jcq4tBWgyH4/2JsojNBSRaN0mcItVMchO+lynonrlqc=", - "h1:Y4Vv/2RdP0Q+uxqhOxzOdKxuuEMjXPDcU0vPc5bCQzI=", - "h1:a0gW8FBKsbP9Fi0HEDoy49WIbEWVHk9+BR4/iwuBdDQ=", - "h1:gElv6iqJtg8OKN77gbw+MjrkrQmJHPkkMEi1J+0xkpU=", - "h1:oslXUugD/NQ+duJgT4BhKQyfGbuFOANknMvR73fiOeM=", - "h1:pPItIWii5oymR+geZB219ROSPuSODPLTlM4S/u8xLvM=", - "h1:u67GWw8GwD9NDlDzp9Y5VRnSQGcCrE8rSpkGPaBpDl0=", - "h1:uUUa9dY0XQOycI8pxg16PFFtL0WCTi9uEJz8trTQ7pU=", - "h1:y3rV8KF2q6GEMANNlf5EkKJurlfbKlIKpjGcdxoy7pQ=", - "zh:0c904ce31a4c6c4a5b3bf7ff1560e77c0cc7e2450c8553ded8e8c90398e1418b", - "zh:36183d310c36373fe4cb936b83c595c6fd3b0a94bc7827f28e5789ccbf59752e", - "zh:556a568a6f0235e8f41647de9e4d3a1e7b1d6502df8b19b54ec441f1c653ea10", - "zh:633ebbd5b0245e75e500ef9be4d9e62288f97e8da3baaa51323892a786d90285", - "zh:6acfe60cf52a65ba8f044f748548d2119e7f4fd7f8ebcb14698960d87c68f529", + "h1:2uxVaMaGPDJljXqYzuD6xtyfOKdNudtGIgawRTkFJIs=", + "h1:34HL1GDpqYdn2iEC+85pCi+NddZf30qH3fJ8y3P6CAM=", + "h1:4Z+uD+YY/7HrqdypM7DyUK+RtWepoLBc9fqtRQYZx4I=", + "h1:8Ss6wlJL2BHvkIZU1iaHvyUntqNhPFZ0fl6mjsxhctc=", + "h1:AYeWWMTWaNg01N3hivxr/6YlG41j1RietMnft9vLjUI=", + "h1:BGRNOzo8NUgbx5RwpWkWmr38f/s3txb7mzQhqfM5blI=", + "h1:EEXRMdC8bPlpLuOU8u+dE8mFJe3BpquFdH6vXcKseY4=", + "h1:ENp29e6O6OnaHJlbtmRlyjlAmm3Wt4mGctWUpBY3d0g=", + "h1:IAtBAbGVe1npj/n1NvCIZkSYGl7qaPYT+FnVdZ2CZ00=", + "h1:QRGGKngrxLORr4CImKXLDDJL5s67zUNBDAgk6FIIFNk=", + "h1:bNql6cC+MfDbaJK3q7a8C1m3A21o7e5mrjBIGtk0RdI=", + "h1:gmNv+PEOkYDdu+c15bfpcoAE3EI7pgmov9RMf3GZLsI=", + "h1:yieZ7NWRYQcLYHUevzDDrrmDkoQICM+3ZeX4i/mw9ME=", + "zh:08b305329a680a9213b2d8e642fbce7e4d97a524b1d2cef59e190ba9d678c477", + "zh:47975bd711ee18a46e589822171fa87474a552b332bfc8dea8fd1a64504eed8d", + "zh:5640d0d226bbafff3395542456c29feb942ca9c55ac01b4245a34c0590a33363", + "zh:5b0ad839fafba938c60a95d6b4a865843643591e97573ed2b9c4af3754064f74", "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", - "zh:904acc31ebb9d6ef68c792074b30532ee61bf515f19e0a3c75b46f126cca1f13", - "zh:a1d0a81246afc8750286d3f6fe7a8fbe6460dd2662407b28dbfbabb612e5fa9d", - "zh:a41a36fe253fc365fe2b7ffc749624688b2693b4634862fda161179ab100029f", - "zh:a7ef269e77ffa8715c8945a2c14322c7ff159ea44c15f62505f3cbb2cae3b32d", - "zh:b01aa3bed30610633b762df64332b26f8844a68c3960cebcb30f04918efc67fe", - "zh:b069cc2cd18cae10757df3ae030508eac8d55de7e49eda7a5e3e11f2f7fe6455", - "zh:b2d2c6313729ebb7465dceece374049e2d08bda34473901be9ff46a8836d42b2", - "zh:db0e114edaf4bc2f3d4769958807c83022bfbc619a00bdf4c4bd17faa4ab2d8b", - "zh:ecc0aa8b9044f664fd2aaf8fa992d976578f78478980555b4b8f6148e8d1a5fe", + "zh:93a9bc1139f5c02a44fdbf51fb2ce0891e2a42033b66febb928ccf72e870408f", + "zh:977f75cdf365686aa16ae02dcc0fc1769bae6f86be6e165393756c940bfb4af0", + "zh:9afcda2660b3dc6ee6329acea532a719225bd1f6cf46f695feb2d77160847c1b", + "zh:9e3da67b1b05b03d1f0c18b8677edf3797815b5dd49629f10eb2f457dccbed30", + "zh:b8d7da230f5266367c1b6b1cf31aa39087e231a87d55b71bb9d5c854ca1fcfe6", + "zh:cc96e7cd7350456b7a11a53d1c76c73a542de8359f9750f637ff865e8e77be91", + "zh:da1f58d067def243047bb7178cb197e2b9c3a791a9eb380ad92b73290615ae29", + "zh:ed5f3e1f59a338bcdce0a2537a8a2f299cfe18c168626799b2a9c97af3d8d3cb", + "zh:f16cc31f73a58a26ffe2d93223eddb7c340623a4e2ef38c6091766fd611e3e11", ] } diff --git a/deployment/modules/cloudflare/docs/config.tf b/deployment/modules/cloudflare/docs/config.tf index 7c59cdd2e3af75..ea1a441a4b087c 100644 --- a/deployment/modules/cloudflare/docs/config.tf +++ b/deployment/modules/cloudflare/docs/config.tf @@ -5,7 +5,7 @@ terraform { required_providers { cloudflare = { source = "cloudflare/cloudflare" - version = "4.52.7" + version = "4.52.8" } } } From 33cba2f9af4f7b2682d16b77d4fff8487977bcad Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 10:59:29 +0200 Subject: [PATCH 07/14] chore(deps): update grafana/grafana docker tag to v12.4.5-ubuntu (#29670) --- docker/docker-compose.prod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml index d30aa99e910ce3..fdc0e7b1fbbe1e 100644 --- a/docker/docker-compose.prod.yml +++ b/docker/docker-compose.prod.yml @@ -97,7 +97,7 @@ services: command: ['./run.sh', '-disable-reporting'] ports: - 3000:3000 - image: grafana/grafana:12.4.4-ubuntu@sha256:df2e7ef5f32f771794cf76bad5f2bceac227036460a2cc269a9045e5662abc58 + image: grafana/grafana:12.4.5-ubuntu@sha256:00396460e499415c828b7c298f19287c8a0f95e72412ee37ac11723655c2d6b9 volumes: - grafana-data:/var/lib/grafana From 2d3cf8ffaaff928a4edae2eabb1fd01953d21a47 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 11:16:09 +0200 Subject: [PATCH 08/14] chore(deps): update dependency terragrunt to v1.1.1 (#29399) --- deployment/mise.toml | 2 +- mise.lock | 44 ++++++++++++++++++++++---------------------- mise.toml | 2 +- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/deployment/mise.toml b/deployment/mise.toml index fadc28dc2e0c8a..24012b155edee7 100644 --- a/deployment/mise.toml +++ b/deployment/mise.toml @@ -1,5 +1,5 @@ [tools] -terragrunt = "1.0.3" +terragrunt = "1.1.1" opentofu = "1.11.6" [tasks."tg:fmt"] diff --git a/mise.lock b/mise.lock index 0eabf0ca4e08ef..049c0f31e83632 100644 --- a/mise.lock +++ b/mise.lock @@ -327,40 +327,40 @@ url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/478563981" provenance = "github-attestations" [[tools.terragrunt]] -version = "1.0.3" +version = "1.1.1" backend = "aqua:gruntwork-io/terragrunt" [tools.terragrunt."platforms.linux-arm64"] -checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe" -url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz" -url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654253" +checksum = "sha256:a374a7993ff3d99665a7e014007d3647ec7f0465c9d55c85e9f94c932e73cea2" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.1.1/terragrunt_linux_arm64.tar.gz" +url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/476786148" [tools.terragrunt."platforms.linux-arm64-musl"] -checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe" -url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz" -url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654253" +checksum = "sha256:a374a7993ff3d99665a7e014007d3647ec7f0465c9d55c85e9f94c932e73cea2" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.1.1/terragrunt_linux_arm64.tar.gz" +url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/476786148" [tools.terragrunt."platforms.linux-x64"] -checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073" -url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz" -url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654196" +checksum = "sha256:ce90077ac31ef17a2ba10d11d45f36c6501997a8f4f79d703bb7daba37032f53" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.1.1/terragrunt_linux_amd64.tar.gz" +url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/476785968" [tools.terragrunt."platforms.linux-x64-musl"] -checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073" -url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz" -url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654196" +checksum = "sha256:ce90077ac31ef17a2ba10d11d45f36c6501997a8f4f79d703bb7daba37032f53" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.1.1/terragrunt_linux_amd64.tar.gz" +url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/476785968" [tools.terragrunt."platforms.macos-arm64"] -checksum = "sha256:aacb5be2ca5475300cbce246dfbd8a45eb47510fbaa70fab8561c49ef5db03aa" -url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_arm64.tar.gz" -url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654084" +checksum = "sha256:9ec8f678b9ae6c81d5d9d77b94cbf6349ce639d5938694b4adc9dea73e416794" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.1.1/terragrunt_darwin_arm64.tar.gz" +url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/476785628" [tools.terragrunt."platforms.macos-x64"] -checksum = "sha256:3133c2251e191aede8e3dd2a5b3aee2e91c5f08f88f117aee40eed9a24c8ef6b" -url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_amd64.tar.gz" -url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406653970" +checksum = "sha256:73e768a69fa44a60f9d9174f54aaf179327e08706f633c9c79d5f5c9622c91c2" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.1.1/terragrunt_darwin_amd64.tar.gz" +url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/476785520" [tools.terragrunt."platforms.windows-x64"] -checksum = "sha256:183b2745b4e04980a6bfa4450ff81956a12596ca22d70f7aaa793980f5b036db" -url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_windows_amd64.exe.tar.gz" -url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654412" +checksum = "sha256:dd50a324691e072a3ac879e9ec43d6310e2936c929fda658eb2811fefcc75115" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.1.1/terragrunt_windows_amd64.exe.tar.gz" +url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/476786421" diff --git a/mise.toml b/mise.toml index d3db6eb783c016..5d33d50376341a 100644 --- a/mise.toml +++ b/mise.toml @@ -17,7 +17,7 @@ config_roots = [ [tools] node = "24.15.0" pnpm = "11.13.1" -terragrunt = "1.0.3" +terragrunt = "1.1.1" opentofu = "1.11.6" "npm:oazapfts" = "7.5.0" "github:extism/cli" = "1.6.3" From 8bbc25270331dd789e0cd7749a80b3b4e7f8fc07 Mon Sep 17 00:00:00 2001 From: Ben Beckford Date: Wed, 22 Jul 2026 02:20:57 -0700 Subject: [PATCH 09/14] feat: exif metadata workflow filter (#29295) --- packages/plugin-core/manifest.json | 70 ++++++++++++++++++++++++++ packages/plugin-core/src/index.ts | 80 ++++++++++++++++++------------ 2 files changed, 117 insertions(+), 33 deletions(-) diff --git a/packages/plugin-core/manifest.json b/packages/plugin-core/manifest.json index d55ad43e7064e9..a72d60c04d54b6 100644 --- a/packages/plugin-core/manifest.json +++ b/packages/plugin-core/manifest.json @@ -308,6 +308,76 @@ }, "uiHints": ["Filter"] }, + { + "name": "assetExifFilter", + "title": "Filter by EXIF metadata", + "description": "Filter assets by their EXIF properties", + "types": ["AssetV1"], + "schema": { + "type": "object", + "properties": { + "property": { + "title": "Property", + "description": "EXIF property to match", + "type": "string", + "enum": [ + "make", + "model", + "exifImageWidth", + "exifImageHeight", + "fileSizeInByte", + "orientation", + "lensModel", + "fNumber", + "focalLength", + "iso", + "description", + "fps", + "exposureTime", + "livePhotoCID", + "timeZone", + "projectionType", + "profileDescription", + "colorspace", + "bitsPerSample", + "rating" + ], + "uiHint": { + "order": 1 + } + }, + "pattern": { + "type": "string", + "title": "Pattern", + "description": "Text or regex pattern to match against property value", + "uiHint": { + "order": 2 + } + }, + "matchType": { + "type": "string", + "title": "Match type", + "enum": ["contains", "startsWith", "exact", "regex"], + "default": "contains", + "description": "Type of pattern matching to perform", + "uiHint": { + "order": 3 + } + }, + "caseSensitive": { + "type": "boolean", + "default": false, + "title": "Case sensitive", + "description": "Whether matching should be case-sensitive", + "uiHint": { + "order": 4 + } + } + }, + "required": ["property", "pattern"] + }, + "uiHints": ["Filter"] + }, { "name": "assetArchive", "title": "Archive asset", diff --git a/packages/plugin-core/src/index.ts b/packages/plugin-core/src/index.ts index d412ad77084ab1..e3f180f98bcca0 100644 --- a/packages/plugin-core/src/index.ts +++ b/packages/plugin-core/src/index.ts @@ -2,6 +2,42 @@ import { wrapper } from '@immich/plugin-sdk'; import { AssetVisibility } from '@immich/sdk'; import type { Manifest } from '../dist/index.d.ts'; +type MatchValueConfig = { + pattern: string; + matchType?: 'contains' | 'exact' | 'regex' | 'startsWith'; + caseSensitive?: boolean; +}; + +const matchValueResult = (value: string, config: MatchValueConfig) => { + const { pattern, matchType = 'contains', caseSensitive = false } = config; + const searchName = caseSensitive ? value : value.toLowerCase(); + const searchPattern = caseSensitive ? pattern : pattern.toLowerCase(); + + switch (matchType) { + case 'contains': { + return { workflow: { continue: searchName.includes(searchPattern) } }; + } + + case 'exact': { + return { workflow: { continue: searchName === searchPattern } }; + } + + case 'startsWith': { + return { workflow: { continue: searchName.startsWith(searchPattern) } }; + } + + case 'regex': { + const flags = caseSensitive ? '' : 'i'; + const regex = new RegExp(searchPattern, flags); + return { workflow: { continue: regex.test(value) } }; + } + + default: { + return {}; + } + } +}; + const methods = wrapper({ assetAddToAlbums: ({ config, data, functions }) => { const assetId = data.asset.id; @@ -53,39 +89,7 @@ const methods = wrapper({ } }, - assetFileFilter: ({ data, config }) => { - const { pattern, matchType = 'contains', caseSensitive = false, usePath = false } = config; - - const { asset } = data; - - const fileName = usePath ? asset.originalPath : asset.originalFileName; - const searchName = caseSensitive ? fileName : fileName.toLowerCase(); - const searchPattern = caseSensitive ? pattern : pattern.toLowerCase(); - - switch (matchType) { - case 'contains': { - return { workflow: { continue: searchName.includes(searchPattern) } }; - } - - case 'exact': { - return { workflow: { continue: searchName === searchPattern } }; - } - - case 'startsWith': { - return { workflow: { continue: searchName.startsWith(searchPattern) } }; - } - - case 'regex': { - const flags = caseSensitive ? '' : 'i'; - const regex = new RegExp(searchPattern, flags); - return { workflow: { continue: regex.test(fileName) } }; - } - - default: { - return {}; - } - } - }, + assetFileFilter: ({ data, config }) => matchValueResult(data.asset.originalFileName || '', config), assetLocationFilter: ({ config, data }) => { if ( @@ -124,6 +128,14 @@ const methods = wrapper({ return { workflow: { continue: earthDiameter * delta <= (config.coordinate?.radius ?? 0) } }; }, + assetExifFilter: ({ config, data }) => { + if (!data.asset.exifInfo || data.asset.exifInfo[config.property] === null) { + return { workflow: { continue: false } }; + } + + return matchValueResult(String(data.asset.exifInfo[config.property]), config); + }, + assetDateFilter: ({ config, data }) => { const assetDate = new Date(data.asset.localDateTime); let startDate = new Date(config.startDate.year, config.startDate.month - 1, config.startDate.day); @@ -200,6 +212,7 @@ const { assetFavorite, assetFileFilter, assetLocationFilter, + assetExifFilter, assetDateFilter, assetLock, assetMissingTimeZoneFilter, @@ -217,6 +230,7 @@ export { assetFavorite, assetFileFilter, assetLocationFilter, + assetExifFilter, assetDateFilter, assetLock, assetMissingTimeZoneFilter, From 8435d219e4206dca41ad648c13cadd511c0c5621 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 09:30:08 +0000 Subject: [PATCH 10/14] chore(deps): update dependency opentofu to v1.12.4 (#28491) --- deployment/mise.toml | 2 +- mise.lock | 44 ++++++++++++++++++++++---------------------- mise.toml | 2 +- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/deployment/mise.toml b/deployment/mise.toml index 24012b155edee7..2e26da09f6db41 100644 --- a/deployment/mise.toml +++ b/deployment/mise.toml @@ -1,6 +1,6 @@ [tools] terragrunt = "1.1.1" -opentofu = "1.11.6" +opentofu = "1.12.4" [tasks."tg:fmt"] run = "terragrunt hclfmt" diff --git a/mise.lock b/mise.lock index 049c0f31e83632..bab6b6902d6e71 100644 --- a/mise.lock +++ b/mise.lock @@ -248,43 +248,43 @@ version = "7.5.0" backend = "npm:oazapfts" [[tools.opentofu]] -version = "1.11.6" +version = "1.12.4" backend = "aqua:opentofu/opentofu" [tools.opentofu."platforms.linux-arm64"] -checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850" -url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz" -url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536382" +checksum = "sha256:dc7bfcd93ce9795a86c58fbf71efd013c39dcd1febb13c9cd3555c43b9c2403a" +url = "https://github.com/opentofu/opentofu/releases/download/v1.12.4/tofu_1.12.4_linux_arm64.tar.gz" +url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/475646678" [tools.opentofu."platforms.linux-arm64-musl"] -checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850" -url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz" -url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536382" +checksum = "sha256:dc7bfcd93ce9795a86c58fbf71efd013c39dcd1febb13c9cd3555c43b9c2403a" +url = "https://github.com/opentofu/opentofu/releases/download/v1.12.4/tofu_1.12.4_linux_arm64.tar.gz" +url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/475646678" [tools.opentofu."platforms.linux-x64"] -checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e" -url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz" -url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536401" +checksum = "sha256:81836d0f12b4fe9013b85586349f993def9429b6383bb77cdd6c2f3a9d9aac24" +url = "https://github.com/opentofu/opentofu/releases/download/v1.12.4/tofu_1.12.4_linux_amd64.tar.gz" +url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/475646677" [tools.opentofu."platforms.linux-x64-musl"] -checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e" -url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz" -url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536401" +checksum = "sha256:81836d0f12b4fe9013b85586349f993def9429b6383bb77cdd6c2f3a9d9aac24" +url = "https://github.com/opentofu/opentofu/releases/download/v1.12.4/tofu_1.12.4_linux_amd64.tar.gz" +url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/475646677" [tools.opentofu."platforms.macos-arm64"] -checksum = "sha256:62d7fa8539e13b444827aa0a3b90c5972da5c47e8f8882d9dcf2e430e78840c1" -url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_arm64.tar.gz" -url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536399" +checksum = "sha256:7c06e4390d9ccd467773e37ff1c3d833c7ca0c24742cd9e9ad47284bea472247" +url = "https://github.com/opentofu/opentofu/releases/download/v1.12.4/tofu_1.12.4_darwin_arm64.tar.gz" +url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/475646544" [tools.opentofu."platforms.macos-x64"] -checksum = "sha256:1408cdef1c380f914565e6b4bb70794c6b163f195fcb233357f3d6c5745906b6" -url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_amd64.tar.gz" -url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536384" +checksum = "sha256:ead1d2ce643addb4ffeb93240b9377ae1c2fd793a6bd22d65922ac37adfdf546" +url = "https://github.com/opentofu/opentofu/releases/download/v1.12.4/tofu_1.12.4_darwin_amd64.tar.gz" +url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/475646688" [tools.opentofu."platforms.windows-x64"] -checksum = "sha256:27323f70c875b8251bfd7e61a4cffc3ebff4e56ed1e611b955016f0c7077367e" -url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_windows_amd64.tar.gz" -url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536406" +checksum = "sha256:a4d86a07755c8d151f20f945e6cfb5b40deeed942af36a9bd385c5c2e965d5dd" +url = "https://github.com/opentofu/opentofu/releases/download/v1.12.4/tofu_1.12.4_windows_amd64.tar.gz" +url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/475646543" [[tools.pnpm]] version = "11.13.1" diff --git a/mise.toml b/mise.toml index 5d33d50376341a..3e45d24223f036 100644 --- a/mise.toml +++ b/mise.toml @@ -18,7 +18,7 @@ config_roots = [ node = "24.15.0" pnpm = "11.13.1" terragrunt = "1.1.1" -opentofu = "1.11.6" +opentofu = "1.12.4" "npm:oazapfts" = "7.5.0" "github:extism/cli" = "1.6.3" "github:webassembly/binaryen" = "version_124" From 8a875f59787a90817ef4300a79d64cc49d6cbf3f Mon Sep 17 00:00:00 2001 From: okxint <130782884+okxint@users.noreply.github.com> Date: Wed, 22 Jul 2026 15:23:45 +0530 Subject: [PATCH 11/14] fix(web): use correct date field for shift-click range in Recently Added (#30071) --- .../internal/search-support.svelte.ts | 13 ++++-- .../timeline-manager.svelte.spec.ts | 42 ++++++++++++++++++- .../timeline-manager.svelte.ts | 4 ++ 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/web/src/lib/managers/timeline-manager/internal/search-support.svelte.ts b/web/src/lib/managers/timeline-manager/internal/search-support.svelte.ts index 8a73bcce7dcf63..d6104f40517b10 100644 --- a/web/src/lib/managers/timeline-manager/internal/search-support.svelte.ts +++ b/web/src/lib/managers/timeline-manager/internal/search-support.svelte.ts @@ -1,6 +1,6 @@ -import { AssetOrder, type AssetResponseDto } from '@immich/sdk'; +import { AssetOrder, AssetOrderBy, type AssetResponseDto } from '@immich/sdk'; import { DateTime } from 'luxon'; -import { plainDateTimeCompare, type TimelineYearMonth } from '$lib/utils/timeline-util'; +import { getOrderingDate, plainDateTimeCompare, type TimelineYearMonth } from '$lib/utils/timeline-util'; import { TimelineManager } from '../timeline-manager.svelte'; import type { TimelineMonth } from '../timeline-month.svelte'; import type { AssetDescriptor, Direction, TimelineAsset } from '../types'; @@ -125,7 +125,14 @@ export async function retrieveRange(timelineManager: TimelineManager, start: Ass return []; } const assetOrder: AssetOrder = timelineManager.getAssetOrder(); - if (plainDateTimeCompare(assetOrder === AssetOrder.Desc, startAsset.localDateTime, endAsset.localDateTime) < 0) { + const orderBy: AssetOrderBy = timelineManager.getOrderBy(); + if ( + plainDateTimeCompare( + assetOrder === AssetOrder.Desc, + getOrderingDate(startAsset, orderBy), + getOrderingDate(endAsset, orderBy), + ) < 0 + ) { [startAsset, endAsset] = [endAsset, startAsset]; // eslint-disable-next-line no-useless-assignment [startTimelineMonth, endTimelineMonth] = [endTimelineMonth, startTimelineMonth]; diff --git a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.spec.ts b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.spec.ts index 17625538979f18..f44f8a30464418 100644 --- a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.spec.ts +++ b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.spec.ts @@ -1,4 +1,4 @@ -import { AssetVisibility, type AssetResponseDto, type TimeBucketAssetResponseDto } from '@immich/sdk'; +import { AssetOrderBy, AssetVisibility, type AssetResponseDto, type TimeBucketAssetResponseDto } from '@immich/sdk'; import { tick } from 'svelte'; import { sdkMock } from '$lib/__mocks__/sdk.mock'; import { eventManager } from '$lib/managers/event-manager.svelte'; @@ -808,4 +808,44 @@ describe('TimelineManager', () => { expect(b.showAssetOwners).toBe(true); }); }); + + describe('retrieveRange', () => { + it('uses createdAt ordering in the Recently Added view (orderBy=CreatedAt)', async () => { + // Simulate the "Recently Added" bug: two assets whose localDateTime order is + // the reverse of their createdAt (upload) order. Before the fix, retrieveRange + // compared localDateTime and selected the wrong range direction. + const timelineManager = new TimelineManager(); + sdkMock.getTimeBuckets.mockResolvedValue([]); + await timelineManager.updateOptions({ orderBy: AssetOrderBy.CreatedAt }); + + // assetA was taken recently (2024) but uploaded first (2025-01) + const assetA = timelineAssetFactory.build({ + localDateTime: fromISODateTimeUTCToObject('2024-06-01T00:00:00.000Z'), + createdAt: fromISODateTimeUTCToObject('2025-01-20T00:00:00.000Z'), + fileCreatedAt: fromISODateTimeUTCToObject('2025-01-20T00:00:00.000Z'), + }); + // assetB was taken long ago (2018) but uploaded second (2025-02) + const assetB = timelineAssetFactory.build({ + localDateTime: fromISODateTimeUTCToObject('2018-03-15T00:00:00.000Z'), + createdAt: fromISODateTimeUTCToObject('2025-02-10T00:00:00.000Z'), + fileCreatedAt: fromISODateTimeUTCToObject('2025-02-10T00:00:00.000Z'), + }); + // assetC is an unrelated asset that should not appear in the selection + const assetC = timelineAssetFactory.build({ + localDateTime: fromISODateTimeUTCToObject('2022-01-01T00:00:00.000Z'), + createdAt: fromISODateTimeUTCToObject('2025-03-01T00:00:00.000Z'), + fileCreatedAt: fromISODateTimeUTCToObject('2025-03-01T00:00:00.000Z'), + }); + + timelineManager.upsertAssets([assetA, assetB, assetC]); + + // Shift-click from assetB (uploaded most recently) to assetA — the range + // between them in the "Recently Added" timeline should contain only those two. + const range = await timelineManager.retrieveRange({ id: assetB.id }, { id: assetA.id }); + const ids = range.map((a) => a.id); + expect(ids).toContain(assetA.id); + expect(ids).toContain(assetB.id); + expect(ids).not.toContain(assetC.id); + }); + }); }); diff --git a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts index 0b9c6d4b078bdd..3f8277fbc49119 100644 --- a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts +++ b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts @@ -625,6 +625,10 @@ export class TimelineManager extends VirtualScrollManager { return this.#options.order ?? AssetOrder.Desc; } + getOrderBy() { + return this.#options.orderBy ?? AssetOrderBy.TakenAt; + } + protected postCreateSegments(): void { this.months.sort((a, b) => { return a.yearMonth.year === b.yearMonth.year From 20997823d8c234ce514aaaead72bdf85126627fb Mon Sep 17 00:00:00 2001 From: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com> Date: Wed, 22 Jul 2026 12:55:47 +0200 Subject: [PATCH 12/14] chore: mise monorepo support is now stable (#30131) --- mise.lock | 42 +++++++++++++++++++++--------------------- mise.toml | 3 +-- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/mise.lock b/mise.lock index bab6b6902d6e71..a400a1e5c74b80 100644 --- a/mise.lock +++ b/mise.lock @@ -94,23 +94,6 @@ url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets version = "7.1.3-6" backend = "github:jellyfin/jellyfin-ffmpeg" -[tools."github:jellyfin/jellyfin-ffmpeg".options] -asset_pattern = "jellyfin-ffmpeg_*_portable_linux64-gpl.tar.xz" - -[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-x64"] -checksum = "sha256:39e99a7927468a6abec5f65d00f55010e8ff2ae3c2605294f179c94f6ae21af2" -url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linux64-gpl.tar.xz" -url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048879" - -[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-x64-musl"] -checksum = "sha256:39e99a7927468a6abec5f65d00f55010e8ff2ae3c2605294f179c94f6ae21af2" -url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linux64-gpl.tar.xz" -url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048879" - -[[tools."github:jellyfin/jellyfin-ffmpeg"]] -version = "7.1.3-6" -backend = "github:jellyfin/jellyfin-ffmpeg" - [tools."github:jellyfin/jellyfin-ffmpeg".options] asset_pattern = "jellyfin-ffmpeg_*_portable_linuxarm64-gpl.tar.xz" @@ -128,10 +111,18 @@ url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets version = "7.1.3-6" backend = "github:jellyfin/jellyfin-ffmpeg" -[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.windows-x64"] -checksum = "sha256:7b7168149689610296f3a187c717056ce0786cc125a31caf28056737e9ba1cc1" -url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_win64-clang-gpl.zip" -url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409036094" +[tools."github:jellyfin/jellyfin-ffmpeg".options] +asset_pattern = "jellyfin-ffmpeg_*_portable_linux64-gpl.tar.xz" + +[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-x64"] +checksum = "sha256:39e99a7927468a6abec5f65d00f55010e8ff2ae3c2605294f179c94f6ae21af2" +url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linux64-gpl.tar.xz" +url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048879" + +[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-x64-musl"] +checksum = "sha256:39e99a7927468a6abec5f65d00f55010e8ff2ae3c2605294f179c94f6ae21af2" +url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linux64-gpl.tar.xz" +url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048879" [[tools."github:jellyfin/jellyfin-ffmpeg"]] version = "7.1.3-6" @@ -145,6 +136,15 @@ checksum = "sha256:066ede9774aaae97a18098aaeea8b7e0d286653eb8618f640476e99c59a53 url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_mac64-gpl.tar.xz" url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/408995889" +[[tools."github:jellyfin/jellyfin-ffmpeg"]] +version = "7.1.3-6" +backend = "github:jellyfin/jellyfin-ffmpeg" + +[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.windows-x64"] +checksum = "sha256:7b7168149689610296f3a187c717056ce0786cc125a31caf28056737e9ba1cc1" +url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_win64-clang-gpl.zip" +url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409036094" + [[tools."github:webassembly/binaryen"]] version = "version_124" backend = "github:webassembly/binaryen" diff --git a/mise.toml b/mise.toml index 3e45d24223f036..0b11993aa93a16 100644 --- a/mise.toml +++ b/mise.toml @@ -1,4 +1,4 @@ -experimental_monorepo_root = true +monorepo_root = true [monorepo] config_roots = [ @@ -35,7 +35,6 @@ macos-x64 = { asset_pattern = "jellyfin-ffmpeg_*_portable_mac64-gpl.tar.xz" } macos-arm64 = { asset_pattern = "jellyfin-ffmpeg_*_portable_macarm64-gpl.tar.xz" } [settings] -experimental = true pin = true lockfile = true From a0c7b811422921285364850cd72347c4ab853105 Mon Sep 17 00:00:00 2001 From: Aditya Raj Singh Date: Wed, 22 Jul 2026 16:30:52 +0530 Subject: [PATCH 13/14] fix(server): dissolve stack when its non-primary assets are deleted (#29354) --- server/src/services/asset.service.spec.ts | 28 +++++++++++++++++++++++ server/src/services/asset.service.ts | 25 ++++++++++++-------- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/server/src/services/asset.service.spec.ts b/server/src/services/asset.service.spec.ts index 75e7fc5e876ecf..0264484bfcfc09 100755 --- a/server/src/services/asset.service.spec.ts +++ b/server/src/services/asset.service.spec.ts @@ -568,6 +568,34 @@ describe(AssetService.name, () => { expect(mocks.stack.delete).toHaveBeenCalledWith(asset.stackId); }); + it('should delete the stack when a non-primary asset is deleted and only the primary would remain', async () => { + const asset = AssetFactory.from().build(); + const deletionAsset = { + ...getForAssetDeletion(asset), + stack: { id: newUuid(), primaryAssetId: newUuid(), assets: [{ id: asset.id }] }, + }; + mocks.stack.delete.mockResolvedValue(); + mocks.assetJob.getForAssetDeletion.mockResolvedValue(deletionAsset); + + await sut.handleAssetDeletion({ id: asset.id, deleteOnDisk: true }); + + expect(mocks.stack.delete).toHaveBeenCalledWith(deletionAsset.stack.id); + }); + + it('should keep the stack when a non-primary asset is deleted and the primary plus another asset remain', async () => { + const asset = AssetFactory.from().build(); + const deletionAsset = { + ...getForAssetDeletion(asset), + stack: { id: newUuid(), primaryAssetId: newUuid(), assets: [{ id: asset.id }, { id: newUuid() }] }, + }; + mocks.assetJob.getForAssetDeletion.mockResolvedValue(deletionAsset); + + await sut.handleAssetDeletion({ id: asset.id, deleteOnDisk: true }); + + expect(mocks.stack.delete).not.toHaveBeenCalled(); + expect(mocks.stack.update).not.toHaveBeenCalled(); + }); + it('should delete a live photo', async () => { const motionAsset = AssetFactory.from({ type: AssetType.Video, visibility: AssetVisibility.Hidden }).build(); const asset = AssetFactory.create({ livePhotoVideoId: motionAsset.id }); diff --git a/server/src/services/asset.service.ts b/server/src/services/asset.service.ts index c27bbd4b18a48e..1edccf9483f35b 100644 --- a/server/src/services/asset.service.ts +++ b/server/src/services/asset.service.ts @@ -316,18 +316,25 @@ export class AssetService extends BaseService { return JobStatus.Failed; } - // replace the parent of the stack children with a new asset - if (asset.stack?.primaryAssetId === id) { - // this only includes timeline visible assets and excludes the primary asset - const stackAssetIds = asset.stack.assets.map((a) => a.id); - if (stackAssetIds.length >= 2) { - const newPrimaryAssetId = stackAssetIds.find((a) => a !== id)!; + if (asset.stack) { + // asset.stack.assets only includes timeline visible assets and excludes the primary asset + const remainingStackAssetIds = asset.stack.assets.map((a) => a.id).filter((assetId) => assetId !== id); + + // the primary survives unless it is the asset being deleted + let remainingCount = remainingStackAssetIds.length; + if (asset.stack.primaryAssetId !== id) { + remainingCount++; + } + + if (remainingCount < 2) { + // 0 or 1 asset would remain: dissolve the stack so it does not linger as a single-asset stack + await this.stackRepository.delete(asset.stack.id); + } else if (asset.stack.primaryAssetId === id) { + // the primary is being deleted but others remain: promote a new primary await this.stackRepository.update(asset.stack.id, { id: asset.stack.id, - primaryAssetId: newPrimaryAssetId, + primaryAssetId: remainingStackAssetIds[0], }); - } else { - await this.stackRepository.delete(asset.stack.id); } } From 12f741d9112afabd6c68319de46232efc7a40cbc Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Wed, 22 Jul 2026 13:25:29 +0200 Subject: [PATCH 14/14] chore(cli): remove -h shorthand for --skip-hash (#30119) Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com> Co-authored-by: Daniel Dietzler --- e2e/src/specs/server/cli/upload.e2e-spec.ts | 2 +- packages/cli/src/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e/src/specs/server/cli/upload.e2e-spec.ts b/e2e/src/specs/server/cli/upload.e2e-spec.ts index 5df36746d76c58..6ed5030e836266 100644 --- a/e2e/src/specs/server/cli/upload.e2e-spec.ts +++ b/e2e/src/specs/server/cli/upload.e2e-spec.ts @@ -650,7 +650,7 @@ describe(`immich upload`, () => { ]); expect(stdout).toBe(''); - expect(stderr).toEqual(`error: option '-n, --dry-run' cannot be used with option '-h, --skip-hash'`); + expect(stderr).toEqual(`error: option '-n, --dry-run' cannot be used with option '--skip-hash'`); expect(exitCode).not.toBe(0); const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) }); diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index c82c7d852b520b..deef7d8c8ccb75 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -47,7 +47,7 @@ program .usage('[paths...] [options]') .addOption(new Option('-r, --recursive', 'Recursive').env('IMMICH_RECURSIVE').default(false)) .addOption(new Option('-i, --ignore ', 'Pattern to ignore').env('IMMICH_IGNORE_PATHS')) - .addOption(new Option('-h, --skip-hash', "Don't hash files before upload").env('IMMICH_SKIP_HASH').default(false)) + .addOption(new Option('--skip-hash', "Don't hash files before upload").env('IMMICH_SKIP_HASH').default(false)) .addOption(new Option('-H, --include-hidden', 'Include hidden folders').env('IMMICH_INCLUDE_HIDDEN').default(false)) .addOption( new Option('-a, --album', 'Automatically create albums based on folder name')