mirror of
https://github.com/immich-app/immich.git
synced 2026-06-22 14:52:17 -07:00
Compare commits
345 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 39fe991451 | |||
| cbe34d7931 | |||
| 06c8d5a183 | |||
| ad9817c582 | |||
| 14f6f2c04f | |||
| 327521fa27 | |||
| 3be803d0c0 | |||
| a364b56b1c | |||
| f9db76433e | |||
| 3f2e51c5d4 | |||
| 430a2bbfd3 | |||
| fbb0bc6e39 | |||
| 8d30cfa2ce | |||
| c9aa9ba711 | |||
| 7cf904acbe | |||
| 983a005709 | |||
| cda499f2b5 | |||
| 12b7cd066b | |||
| 83b4dc1733 | |||
| a9d64b30ad | |||
| 3927eb6755 | |||
| 27cfa0e788 | |||
| 76c042abc7 | |||
| 0f49bcbd27 | |||
| df4a708aed | |||
| 34f78e3fd4 | |||
| 3c2296b818 | |||
| 805ca1a2bf | |||
| 9ee412110f | |||
| 010220d588 | |||
| 10fddf2d51 | |||
| d307ab60ca | |||
| ce59cc9241 | |||
| 1fa034125c | |||
| 8036dc4b8c | |||
| 54895fb10e | |||
| e70a1163f3 | |||
| a23a7c69ae | |||
| f21a753aff | |||
| cc8d3b4107 | |||
| 622a330d82 | |||
| 5e8744a568 | |||
| b633cc4f04 | |||
| a9ee6a7ce9 | |||
| c273ccf2e2 | |||
| 5f1a180d1a | |||
| cc54de87aa | |||
| a97e5999e4 | |||
| 46631b3786 | |||
| 5a3be158b9 | |||
| b21af78454 | |||
| abd62d9295 | |||
| e31d4aa909 | |||
| 43b2d04e2c | |||
| e4dbe777a0 | |||
| d36aed4c5b | |||
| f1da9d2429 | |||
| 232ca3cf3f | |||
| 50f1121459 | |||
| 892397807c | |||
| 714c647937 | |||
| c56f477a0f | |||
| 296cd40da9 | |||
| a17276fd1e | |||
| c3e23a6b3a | |||
| 13a7b4a276 | |||
| 563cff26bf | |||
| e81b6778ca | |||
| aa6af7ce36 | |||
| 59d036a2ed | |||
| 7a5c014558 | |||
| e2954b6411 | |||
| 0fb18ed241 | |||
| c0b3b08ce6 | |||
| e8a1084e5b | |||
| d227ba2d51 | |||
| 9cb94343d1 | |||
| aa126e377c | |||
| 74878628c8 | |||
| 4ead3e697d | |||
| fb798a8f29 | |||
| 07813135b5 | |||
| 92a75b0cd3 | |||
| 8132e8a38c | |||
| 43f2f56530 | |||
| e580bb5d0a | |||
| d3680871ef | |||
| b9b1cc2f65 | |||
| 7d198956a6 | |||
| a7b5f81701 | |||
| 5c38373808 | |||
| 1ce961fbb3 | |||
| 4bc411b7c7 | |||
| 11c1025271 | |||
| 8b5385f94b | |||
| d3438cf4a7 | |||
| 6c5c6a1035 | |||
| c928787b3e | |||
| fe9ca4f40a | |||
| a665cec920 | |||
| 568283a8eb | |||
| f382624e68 | |||
| 24dad15636 | |||
| 7ab533b57b | |||
| d10153bbc7 | |||
| b846afeb08 | |||
| e222b19576 | |||
| 1fee99cd2a | |||
| 70bb7e4b7e | |||
| f973927c68 | |||
| e29267359e | |||
| 164cda87a3 | |||
| 12d344efe0 | |||
| 474efd39f8 | |||
| 9e453440e6 | |||
| 8860817c76 | |||
| 3c108a8d22 | |||
| 8d553d6e9c | |||
| 346b98ed4f | |||
| 60683bd91e | |||
| b6938614b2 | |||
| 98961a1d36 | |||
| 5ae95102b4 | |||
| 216d0ba365 | |||
| 28e42f7e29 | |||
| 733373c0ca | |||
| 5617d6ca7c | |||
| 875dd2dead | |||
| 9043bc8435 | |||
| b3d49045de | |||
| 58528cad08 | |||
| 99281de6ab | |||
| 6268d23d12 | |||
| d7999ce1d1 | |||
| 6b0fd89cd2 | |||
| 4b0adb7a1e | |||
| de70d19d20 | |||
| 7155bb1e80 | |||
| fa08e72d30 | |||
| e2de8c7c53 | |||
| 429e181c8f | |||
| 7f611d9031 | |||
| e94e22f3f8 | |||
| 4a8c3b60be | |||
| 2190aa72a8 | |||
| d21cb28526 | |||
| 5c33eb3204 | |||
| 137687bc0f | |||
| 9d4a6614b1 | |||
| e4352a7817 | |||
| 911dde39c9 | |||
| afa836181c | |||
| 963862b1b9 | |||
| 96d521e149 | |||
| 1bb7517da0 | |||
| 814c2e32e4 | |||
| 92841f311f | |||
| 9d2e576630 | |||
| 936418a464 | |||
| 84c75d95c7 | |||
| 9287fa08c6 | |||
| 408e1180ca | |||
| 07f19d2caa | |||
| 368cb7a4ad | |||
| 109e0a7ad0 | |||
| 59750dad7d | |||
| 13ecfc8876 | |||
| 65d8b35f8b | |||
| 942d3c648c | |||
| 82db8be5ff | |||
| 03554b24ad | |||
| c5fb67c004 | |||
| 40983b46c8 | |||
| 5dcdbf04ea | |||
| da8ed3eceb | |||
| 2afde23a5d | |||
| d57a152040 | |||
| 728e92ea33 | |||
| 138e2d9158 | |||
| 7eabac6702 | |||
| cf4789e008 | |||
| 412884fce3 | |||
| 16aee2b869 | |||
| 3f7af51531 | |||
| 4eb100327e | |||
| 69b1946484 | |||
| 61cd69a286 | |||
| c8a1d0e400 | |||
| d120444a87 | |||
| 2382894fa2 | |||
| a52e7dc11a | |||
| 206992605e | |||
| 65611bb860 | |||
| 14aff51da9 | |||
| c42cea5ca9 | |||
| da8505f61d | |||
| 58586483dc | |||
| a838167f11 | |||
| b189fc571c | |||
| 96923f6115 | |||
| 0d6cce4a5b | |||
| 55947cb227 | |||
| 8783180cf3 | |||
| 134c0d4dfb | |||
| aecf8ec88b | |||
| bcff1d42b0 | |||
| 1bd367bd51 | |||
| 725f266b81 | |||
| d08e3de207 | |||
| 26714f6bfe | |||
| a5ce3fc927 | |||
| 3b23f71a3f | |||
| dec33cadd9 | |||
| 80c15a5e27 | |||
| 936c28a40b | |||
| 1a837a28ac | |||
| 8d5d12b108 | |||
| dd7a94135f | |||
| 1acc511b5c | |||
| 452e88267a | |||
| b941108cbd | |||
| e46f2843f7 | |||
| cf991e7b1b | |||
| 748a13104a | |||
| 2dd6b47714 | |||
| 8682be4774 | |||
| dc66892ca1 | |||
| 53a24783f5 | |||
| 0546bc900c | |||
| 7c25bcc0a7 | |||
| 7905853639 | |||
| 073dcc1fbe | |||
| ccdaa4223c | |||
| 5386b62dc4 | |||
| 9733fa4872 | |||
| 3b34c53092 | |||
| fd7ddfef54 | |||
| 0975b1599c | |||
| 78ac0ade01 | |||
| 7b9dab872b | |||
| 6413495fb8 | |||
| b414b3d32b | |||
| 20da7c4267 | |||
| 92b6778d2d | |||
| 5a61e589e8 | |||
| 85192bb110 | |||
| c7ae97fa2b | |||
| 8d02f3625d | |||
| a5a7380a26 | |||
| d9ce3d2046 | |||
| 815ff677fc | |||
| 915d865ce2 | |||
| c28e5f90b6 | |||
| 4383473ed6 | |||
| 77701dd5a3 | |||
| d4808fdc4d | |||
| 7fa967a98e | |||
| 9cffcc9f4e | |||
| 40925f0a06 | |||
| 0544d22902 | |||
| 3d075f2bf8 | |||
| 7384799f19 | |||
| 4a7f06e8fd | |||
| 8f662fc459 | |||
| 24b1dae9f2 | |||
| 3a3469a5f9 | |||
| 7993619ed2 | |||
| 4d1f6f869b | |||
| 3eb03f7934 | |||
| 03ed3daa31 | |||
| 02581e81a7 | |||
| 3ab3d5cf43 | |||
| 0ef04d9baa | |||
| df016f9228 | |||
| 17779c1e74 | |||
| 01d6a244d8 | |||
| 21d6755f39 | |||
| e91c017dd0 | |||
| 43687cd8b4 | |||
| 06729ee5a5 | |||
| b0c9743d9a | |||
| 37cc028868 | |||
| 84a2b7a3c8 | |||
| 89b3433346 | |||
| 3ff0d47ee3 | |||
| aeaf846482 | |||
| b031548791 | |||
| fcea617313 | |||
| 024f20ea26 | |||
| 0a4ed6fd71 | |||
| b6e2ce1f35 | |||
| e323e778cd | |||
| 6a87797649 | |||
| f4a4649bbc | |||
| 6ca54ee722 | |||
| 8e3035f783 | |||
| 79801595db | |||
| 3e1c8aacb1 | |||
| 91ac56cef2 | |||
| 58beac8fe0 | |||
| f632d320f5 | |||
| 2ddaf6a611 | |||
| 1932c60e1c | |||
| dc6f8e746e | |||
| ad7aedb843 | |||
| 571e6a8560 | |||
| 4791313def | |||
| f88fdae048 | |||
| bcef7aa6b6 | |||
| ce292bdce9 | |||
| 4eee023648 | |||
| 8f4b0fce49 | |||
| c6b3127b35 | |||
| 4d6a50c2cb | |||
| 15f3947ae6 | |||
| e142e3aca7 | |||
| 38438c8d9a | |||
| a278c10c75 | |||
| 2276443c56 | |||
| bb44773e57 | |||
| 14d9e90a03 | |||
| 03e042213c | |||
| db589455f4 | |||
| fb0a54d548 | |||
| 7013cc0904 | |||
| dcaf7b4a65 | |||
| 12f7b2a005 | |||
| 7837d40f57 | |||
| b4f719653f | |||
| f370b4bac6 | |||
| d788169bf3 | |||
| eea820fa2f | |||
| 271f1cb868 | |||
| 8c8dc9d32f | |||
| fd18e55f7c | |||
| faab9e620d | |||
| 5ba3efafd8 | |||
| 8b3c9bf9c3 | |||
| 41f285aa3e | |||
| fdac6c8bc4 | |||
| d7f05d2510 | |||
| 3100bd5eed | |||
| 8a024e2b50 | |||
| 25a6a38b30 | |||
| 7c6750941e |
@@ -75,7 +75,7 @@
|
|||||||
{
|
{
|
||||||
"label": "Build Immich CLI",
|
"label": "Build Immich CLI",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "pnpm --filter cli build:dev"
|
"command": "pnpm --filter @immich/cli build:dev"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ${UPLOAD_LOCATION:-upload-devcontainer-volume}${UPLOAD_LOCATION:+/photos}:/data
|
- ${UPLOAD_LOCATION:-upload-devcontainer-volume}${UPLOAD_LOCATION:+/photos}:/data
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
- pnpm_store_server:/buildcache/pnpm-store
|
- build_cache:/buildcache
|
||||||
- ../plugins:/build/corePlugin
|
- ../packages/plugin-core:/build/plugins/immich-plugin-core
|
||||||
immich-web:
|
immich-web:
|
||||||
env_file: !reset []
|
env_file: !reset []
|
||||||
immich-machine-learning:
|
immich-machine-learning:
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ log "Preparing Immich Web Frontend"
|
|||||||
log ""
|
log ""
|
||||||
run_cmd pnpm --filter @immich/sdk install
|
run_cmd pnpm --filter @immich/sdk install
|
||||||
run_cmd pnpm --filter @immich/sdk build
|
run_cmd pnpm --filter @immich/sdk build
|
||||||
|
run_cmd pnpm --filter @immich/plugin-sdk install
|
||||||
|
run_cmd pnpm --filter @immich/plugin-sdk build
|
||||||
run_cmd pnpm --filter immich-web install
|
run_cmd pnpm --filter immich-web install
|
||||||
|
|
||||||
log "Starting Immich Web Frontend"
|
log "Starting Immich Web Frontend"
|
||||||
|
|||||||
+1
-3
@@ -30,9 +30,7 @@ machine-learning/
|
|||||||
misc/
|
misc/
|
||||||
mobile/
|
mobile/
|
||||||
|
|
||||||
open-api/typescript-sdk/build/
|
packages/sdk/build/
|
||||||
!open-api/typescript-sdk/package.json
|
|
||||||
!open-api/typescript-sdk/package-lock.json
|
|
||||||
|
|
||||||
server/upload/
|
server/upload/
|
||||||
server/src/queries
|
server/src/queries
|
||||||
|
|||||||
+2
-2
@@ -24,7 +24,7 @@ mobile/lib/infrastructure/repositories/db.repository.steps.dart linguist-generat
|
|||||||
mobile/test/drift/main/generated/** -diff -merge
|
mobile/test/drift/main/generated/** -diff -merge
|
||||||
mobile/test/drift/main/generated/** linguist-generated=true
|
mobile/test/drift/main/generated/** linguist-generated=true
|
||||||
|
|
||||||
open-api/typescript-sdk/fetch-client.ts -diff -merge
|
packages/sdk/fetch-client.ts -diff -merge
|
||||||
open-api/typescript-sdk/fetch-client.ts linguist-generated=true
|
packages/sdk/fetch-client.ts linguist-generated=true
|
||||||
|
|
||||||
*.sh text eol=lf
|
*.sh text eol=lf
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
custom: ['https://buy.immich.app', 'https://immich.store']
|
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
cli:
|
cli:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- cli/src/**
|
- packages/cli/src/**
|
||||||
|
|
||||||
documentation:
|
documentation:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
uses_template: ${{ steps.check.outputs.uses_template }}
|
uses_template: ${{ steps.check.outputs.uses_template }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
sparse-checkout: .github/pull_request_template.md
|
sparse-checkout: .github/pull_request_template.md
|
||||||
sparse-checkout-cone-mode: false
|
sparse-checkout-cone-mode: false
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ jobs:
|
|||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
@@ -73,24 +73,31 @@ jobs:
|
|||||||
needs: pre-job
|
needs: pre-job
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
# Skip when PR from a fork
|
pull-requests: write
|
||||||
if: ${{ !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' && fromJSON(needs.pre-job.outputs.should_run).mobile == true }}
|
if: ${{ github.actor != 'dependabot[bot]' && fromJSON(needs.pre-job.outputs.should_run).mobile == true }}
|
||||||
runs-on: mich
|
runs-on: mich
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
ref: ${{ inputs.ref || github.sha }}
|
ref: ${{ inputs.ref }}
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
- name: Setup Mise
|
||||||
|
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
|
||||||
|
with:
|
||||||
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
working_directory: ./mobile
|
||||||
|
|
||||||
- name: Create the Keystore
|
- name: Create the Keystore
|
||||||
|
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||||
env:
|
env:
|
||||||
KEY_JKS: ${{ secrets.KEY_JKS }}
|
KEY_JKS: ${{ secrets.KEY_JKS }}
|
||||||
working-directory: ./mobile
|
working-directory: ./mobile
|
||||||
@@ -110,16 +117,8 @@ jobs:
|
|||||||
~/.gradle/wrapper
|
~/.gradle/wrapper
|
||||||
~/.android/sdk
|
~/.android/sdk
|
||||||
mobile/android/.gradle
|
mobile/android/.gradle
|
||||||
mobile/.dart_tool
|
|
||||||
key: build-mobile-gradle-${{ runner.os }}-main
|
key: build-mobile-gradle-${{ runner.os }}-main
|
||||||
|
|
||||||
- name: Setup Flutter SDK
|
|
||||||
uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0
|
|
||||||
with:
|
|
||||||
channel: 'stable'
|
|
||||||
flutter-version-file: ./mobile/pubspec.yaml
|
|
||||||
cache: true
|
|
||||||
|
|
||||||
- name: Setup Android SDK
|
- name: Setup Android SDK
|
||||||
uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1
|
uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1
|
||||||
with:
|
with:
|
||||||
@@ -130,11 +129,10 @@ jobs:
|
|||||||
run: flutter pub get
|
run: flutter pub get
|
||||||
|
|
||||||
- name: Generate translation file
|
- name: Generate translation file
|
||||||
run: dart run easy_localization:generate -S ../i18n && dart run bin/generate_keys.dart
|
run: mise //mobile:codegen:translation
|
||||||
working-directory: ./mobile
|
|
||||||
|
|
||||||
- name: Generate platform APIs
|
- name: Generate platform APIs
|
||||||
run: make pigeon
|
run: mise //mobile:codegen:pigeon
|
||||||
working-directory: ./mobile
|
working-directory: ./mobile
|
||||||
|
|
||||||
- name: Build Android App Bundle
|
- name: Build Android App Bundle
|
||||||
@@ -144,20 +142,43 @@ jobs:
|
|||||||
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||||
ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
|
ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
|
||||||
IS_MAIN: ${{ github.ref == 'refs/heads/main' }}
|
IS_MAIN: ${{ github.ref == 'refs/heads/main' }}
|
||||||
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
run: |
|
run: |
|
||||||
if [[ $IS_MAIN == 'true' ]]; then
|
if [[ $IS_MAIN == 'true' ]]; then
|
||||||
flutter build apk --release
|
flutter build apk --release
|
||||||
flutter build apk --release --split-per-abi --target-platform android-arm,android-arm64,android-x64
|
flutter build apk --release --split-per-abi --target-platform android-arm,android-arm64,android-x64
|
||||||
else
|
else
|
||||||
flutter build apk --debug --split-per-abi --target-platform android-arm64
|
flutter build apk --release
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Publish Android Artifact
|
- name: Publish Android Artifact
|
||||||
|
id: upload-apk
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
with:
|
with:
|
||||||
name: release-apk-signed
|
name: release-apk-signed
|
||||||
path: mobile/build/app/outputs/flutter-apk/*.apk
|
path: mobile/build/app/outputs/flutter-apk/*.apk
|
||||||
|
|
||||||
|
- name: Comment APK download link on PR
|
||||||
|
if: ${{ github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork }}
|
||||||
|
uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0
|
||||||
|
env:
|
||||||
|
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||||
|
APK_URL: ${{ steps.upload-apk.outputs.artifact-url }}
|
||||||
|
with:
|
||||||
|
id: mobile-android-apk
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
body: |
|
||||||
|
📱 **Android release APK (universal)** — `${{ env.HEAD_SHA }}`
|
||||||
|
|
||||||
|
Download: ${{ env.APK_URL }}
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>QR code</summary>
|
||||||
|
<img src="https://api.qrserver.com/v1/create-qr-code/?size=240x240&data=${{ env.APK_URL }}" alt="QR code" />
|
||||||
|
</details>
|
||||||
|
|
||||||
|
Installs as a separate app (applicationId `app.alextran.immich.pr${{ github.event.pull_request.number }}`), so it coexists with the Play Store version and any other PR builds.
|
||||||
|
|
||||||
- name: Save Gradle Cache
|
- name: Save Gradle Cache
|
||||||
id: cache-gradle-save
|
id: cache-gradle-save
|
||||||
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
@@ -168,7 +189,6 @@ jobs:
|
|||||||
~/.gradle/wrapper
|
~/.gradle/wrapper
|
||||||
~/.android/sdk
|
~/.android/sdk
|
||||||
mobile/android/.gradle
|
mobile/android/.gradle
|
||||||
mobile/.dart_tool
|
|
||||||
key: ${{ steps.cache-gradle-restore.outputs.cache-primary-key }}
|
key: ${{ steps.cache-gradle-restore.outputs.cache-primary-key }}
|
||||||
|
|
||||||
build-sign-ios:
|
build-sign-ios:
|
||||||
@@ -181,36 +201,43 @@ jobs:
|
|||||||
runs-on: macos-15
|
runs-on: macos-15
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
|
with:
|
||||||
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Select Xcode 26
|
- name: Select Xcode 26
|
||||||
run: sudo xcode-select -s /Applications/Xcode_26.2.app/Contents/Developer
|
run: sudo xcode-select -s /Applications/Xcode_26.2.app/Contents/Developer
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
ref: ${{ inputs.ref || github.sha }}
|
ref: ${{ inputs.ref || github.sha }}
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Setup Flutter SDK
|
- name: Setup Mise
|
||||||
uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0
|
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
|
||||||
with:
|
with:
|
||||||
channel: 'stable'
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
flutter-version-file: ./mobile/pubspec.yaml
|
working_directory: ./mobile
|
||||||
cache: true
|
|
||||||
|
|
||||||
- name: Install Flutter dependencies
|
- name: Install Flutter dependencies
|
||||||
working-directory: ./mobile
|
working-directory: ./mobile
|
||||||
run: flutter pub get
|
run: flutter pub get
|
||||||
|
|
||||||
- name: Generate translation files
|
- name: Generate translation files
|
||||||
run: dart run easy_localization:generate -S ../i18n && dart run bin/generate_keys.dart
|
run: mise //mobile:codegen:translation
|
||||||
working-directory: ./mobile
|
|
||||||
|
|
||||||
- name: Generate platform APIs
|
- name: Generate platform APIs
|
||||||
run: make pigeon
|
run: mise //mobile:codegen:pigeon
|
||||||
|
|
||||||
|
- name: Resolve iOS Swift Packages
|
||||||
working-directory: ./mobile
|
working-directory: ./mobile
|
||||||
|
run: flutter build ios --config-only --no-codesign
|
||||||
|
|
||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0
|
uses: ruby/setup-ruby@89f90524b88a01fe6e0b732220432cc6142926af # v1.313.0
|
||||||
with:
|
with:
|
||||||
ruby-version: '3.3'
|
ruby-version: '3.3'
|
||||||
bundler-cache: true
|
bundler-cache: true
|
||||||
@@ -267,7 +294,6 @@ jobs:
|
|||||||
APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
|
APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
|
||||||
APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }}
|
APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }}
|
||||||
ENVIRONMENT: ${{ inputs.environment || 'development' }}
|
ENVIRONMENT: ${{ inputs.environment || 'development' }}
|
||||||
BUNDLE_ID_SUFFIX: ${{ inputs.environment == 'production' && '' || 'development' }}
|
|
||||||
GITHUB_REF: ${{ github.ref }}
|
GITHUB_REF: ${{ github.ref }}
|
||||||
FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT: 120
|
FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT: 120
|
||||||
FASTLANE_XCODEBUILD_SETTINGS_RETRIES: 6
|
FASTLANE_XCODEBUILD_SETTINGS_RETRIES: 6
|
||||||
|
|||||||
@@ -19,13 +19,13 @@ jobs:
|
|||||||
actions: write
|
actions: write
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- 'open-api/**'
|
- 'open-api/**'
|
||||||
|
- 'mobile/lib/utils/openapi_patching.dart'
|
||||||
- '.github/workflows/check-openapi.yml'
|
- '.github/workflows/check-openapi.yml'
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
@@ -19,13 +20,46 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Check for breaking API changes
|
- name: Check for breaking API changes
|
||||||
uses: oasdiff/oasdiff-action/breaking@37bf9ff785c7315df88216660826e71be4cc03da # v0.0.44
|
uses: oasdiff/oasdiff-action/breaking@3530478ec30f84adedbfeb28f0d9527a290f50a9 # v0.0.57
|
||||||
with:
|
with:
|
||||||
base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json
|
base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json
|
||||||
revision: open-api/immich-openapi-specs.json
|
revision: open-api/immich-openapi-specs.json
|
||||||
fail-on: ERR
|
fail-on: ERR
|
||||||
|
|
||||||
|
check-mobile-patches:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Setup Mise
|
||||||
|
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
|
||||||
|
with:
|
||||||
|
github_token: ${{ github.token }}
|
||||||
|
working_directory: ./mobile
|
||||||
|
|
||||||
|
- name: Get packages
|
||||||
|
working-directory: ./mobile
|
||||||
|
run: flutter pub get
|
||||||
|
|
||||||
|
- name: Fetch base spec from main
|
||||||
|
run: |
|
||||||
|
curl -fsSL \
|
||||||
|
"https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json" \
|
||||||
|
-o /tmp/base-spec.json
|
||||||
|
|
||||||
|
- name: Check newly-required fields have a backward-compat patch
|
||||||
|
working-directory: ./mobile
|
||||||
|
env:
|
||||||
|
OPENAPI_BASE_SPEC: /tmp/base-spec.json
|
||||||
|
OPENAPI_REVISION_SPEC: ../open-api/immich-openapi-specs.json
|
||||||
|
run: flutter test test/openapi_patches_coverage.dart
|
||||||
|
|||||||
+23
-31
@@ -3,11 +3,11 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
- 'cli/**'
|
- 'packages/cli/**'
|
||||||
- '.github/workflows/cli.yml'
|
- '.github/workflows/cli.yml'
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- 'cli/**'
|
- 'packages/cli/**'
|
||||||
- '.github/workflows/cli.yml'
|
- '.github/workflows/cli.yml'
|
||||||
release:
|
release:
|
||||||
types: [published]
|
types: [published]
|
||||||
@@ -28,38 +28,30 @@ jobs:
|
|||||||
packages: write
|
packages: write
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: ./cli
|
working-directory: ./packages/cli
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup Mise
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
|
||||||
|
|
||||||
- name: Setup Node
|
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
with:
|
||||||
node-version-file: './cli/.nvmrc'
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
registry-url: 'https://registry.npmjs.org'
|
|
||||||
cache: 'pnpm'
|
|
||||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
|
||||||
|
|
||||||
- name: Setup typescript-sdk
|
- name: Publish
|
||||||
run: pnpm install && pnpm run build
|
|
||||||
working-directory: ./open-api/typescript-sdk
|
|
||||||
|
|
||||||
- run: pnpm install --frozen-lockfile
|
|
||||||
- run: pnpm build
|
|
||||||
- run: pnpm publish --provenance --no-git-checks
|
|
||||||
if: ${{ github.event_name == 'release' }}
|
if: ${{ github.event_name == 'release' }}
|
||||||
|
env:
|
||||||
|
NPM_TAG: ${{ github.event.release.prerelease && 'rc' || 'latest' }}
|
||||||
|
run: mise run ci-publish -- --tag "$NPM_TAG"
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
name: Docker
|
name: Docker
|
||||||
@@ -71,25 +63,25 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
|
uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
@@ -99,12 +91,12 @@ jobs:
|
|||||||
- name: Get package version
|
- name: Get package version
|
||||||
id: package-version
|
id: package-version
|
||||||
run: |
|
run: |
|
||||||
version=$(jq -r '.version' cli/package.json)
|
version=$(jq -r '.version' packages/cli/package.json)
|
||||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Generate docker image tags
|
- name: Generate docker image tags
|
||||||
id: metadata
|
id: metadata
|
||||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
|
||||||
with:
|
with:
|
||||||
flavor: |
|
flavor: |
|
||||||
latest=false
|
latest=false
|
||||||
@@ -112,12 +104,12 @@ jobs:
|
|||||||
name=ghcr.io/${{ github.repository_owner }}/immich-cli
|
name=ghcr.io/${{ github.repository_owner }}/immich-cli
|
||||||
tags: |
|
tags: |
|
||||||
type=raw,value=${{ steps.package-version.outputs.version }},enable=${{ github.event_name == 'release' }}
|
type=raw,value=${{ steps.package-version.outputs.version }},enable=${{ github.event_name == 'release' }}
|
||||||
type=raw,value=latest,enable=${{ github.event_name == 'release' }}
|
type=raw,value=latest,enable=${{ github.event_name == 'release' && !github.event.release.prerelease }}
|
||||||
|
|
||||||
- name: Build and push image
|
- name: Build and push image
|
||||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
|
||||||
with:
|
with:
|
||||||
file: cli/Dockerfile
|
file: packages/cli/Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: ${{ github.event_name == 'release' }}
|
push: ${{ github.event_name == 'release' }}
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
|
|||||||
@@ -14,7 +14,11 @@ jobs:
|
|||||||
should_run: ${{ steps.should_run.outputs.run }}
|
should_run: ${{ steps.should_run.outputs.run }}
|
||||||
steps:
|
steps:
|
||||||
- id: should_run
|
- id: should_run
|
||||||
run: echo "run=${{ github.event_name == 'issues' || github.event.discussion.category.name == 'Feature Request' }}" >> $GITHUB_OUTPUT
|
run: |
|
||||||
|
echo "run=${{
|
||||||
|
(github.event_name == 'issues' || github.event.discussion.category.name == 'Feature Request')
|
||||||
|
&& !contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.issue.author_association || github.event.discussion.author_association)
|
||||||
|
}}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
get_body:
|
get_body:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -35,7 +39,7 @@ jobs:
|
|||||||
needs: [get_body, should_run]
|
needs: [get_body, should_run]
|
||||||
if: ${{ needs.should_run.outputs.should_run == 'true' }}
|
if: ${{ needs.should_run.outputs.should_run == 'true' }}
|
||||||
container:
|
container:
|
||||||
image: ghcr.io/immich-app/mdq:main@sha256:32abe582452b12dff55055e1d6bc24508a8f17164f9d1831db7bb70953c014c6
|
image: ghcr.io/immich-app/mdq:main@sha256:2a64c7f045cb7b580fbdf3614d7d1805f5775fec453e3d1023764180efa8c70b
|
||||||
outputs:
|
outputs:
|
||||||
checked: ${{ steps.get_checkbox.outputs.checked }}
|
checked: ${{ steps.get_checkbox.outputs.checked }}
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -44,20 +44,20 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
|
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# 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)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
|
uses: github/codeql-action/autobuild@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ 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
|
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||||
@@ -83,6 +83,6 @@ jobs:
|
|||||||
# ./location_of_script_within_repo/buildscript.sh
|
# ./location_of_script_within_repo/buildscript.sh
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
|
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||||
with:
|
with:
|
||||||
category: '/language:${{matrix.language}}'
|
category: '/language:${{matrix.language}}'
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ jobs:
|
|||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
@@ -60,7 +60,7 @@ jobs:
|
|||||||
suffix: ['', '-cuda', '-rocm', '-openvino', '-armnn', '-rknn']
|
suffix: ['', '-cuda', '-rocm', '-openvino', '-armnn', '-rknn']
|
||||||
steps:
|
steps:
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -90,7 +90,7 @@ jobs:
|
|||||||
suffix: ['']
|
suffix: ['']
|
||||||
steps:
|
steps:
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -132,7 +132,7 @@ jobs:
|
|||||||
suffixes: '-rocm'
|
suffixes: '-rocm'
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
runner-mapping: '{"linux/amd64": "pokedex-large"}'
|
runner-mapping: '{"linux/amd64": "pokedex-large"}'
|
||||||
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@5813c7c4f7016c748ae7ac5d5f684846649d4d20 # multi-runner-build-workflow-v2.4.0
|
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@50dc3a14f0606ecd8fc28d78d3a3c655115ab695 # multi-runner-build-workflow-v3.1.0
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
actions: read
|
actions: read
|
||||||
@@ -147,7 +147,7 @@ jobs:
|
|||||||
platforms: ${{ matrix.platforms }}
|
platforms: ${{ matrix.platforms }}
|
||||||
runner-mapping: ${{ matrix.runner-mapping }}
|
runner-mapping: ${{ matrix.runner-mapping }}
|
||||||
suffixes: ${{ matrix.suffixes }}
|
suffixes: ${{ matrix.suffixes }}
|
||||||
dockerhub-push: ${{ github.event_name == 'release' }}
|
dockerhub-push: ${{ github.event_name == 'release' && !github.event.release.prerelease }}
|
||||||
build-args: |
|
build-args: |
|
||||||
DEVICE=${{ matrix.device }}
|
DEVICE=${{ matrix.device }}
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ jobs:
|
|||||||
name: Build and Push Server
|
name: Build and Push Server
|
||||||
needs: pre-job
|
needs: pre-job
|
||||||
if: ${{ fromJSON(needs.pre-job.outputs.should_run).server == true }}
|
if: ${{ fromJSON(needs.pre-job.outputs.should_run).server == true }}
|
||||||
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@5813c7c4f7016c748ae7ac5d5f684846649d4d20 # multi-runner-build-workflow-v2.4.0
|
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@50dc3a14f0606ecd8fc28d78d3a3c655115ab695 # multi-runner-build-workflow-v3.1.0
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
actions: read
|
actions: read
|
||||||
@@ -167,7 +167,7 @@ jobs:
|
|||||||
image: immich-server
|
image: immich-server
|
||||||
context: .
|
context: .
|
||||||
dockerfile: server/Dockerfile
|
dockerfile: server/Dockerfile
|
||||||
dockerhub-push: ${{ github.event_name == 'release' }}
|
dockerhub-push: ${{ github.event_name == 'release' && !github.event.release.prerelease }}
|
||||||
build-args: |
|
build-args: |
|
||||||
DEVICE=cpu
|
DEVICE=cpu
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
@@ -54,27 +54,21 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup Mise
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
|
||||||
|
|
||||||
- name: Setup Node
|
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
with:
|
||||||
node-version-file: './docs/.nvmrc'
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
cache: 'pnpm'
|
|
||||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
|
||||||
|
|
||||||
- name: Run install
|
- name: Run install
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
artifact: ${{ steps.get-artifact.outputs.result }}
|
artifact: ${{ steps.get-artifact.outputs.result }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
@@ -98,9 +98,16 @@ jobs:
|
|||||||
shouldDeploy: true
|
shouldDeploy: true
|
||||||
};
|
};
|
||||||
} else if (eventType == "release") {
|
} else if (eventType == "release") {
|
||||||
|
const tag = context.payload.workflow_run.head_branch;
|
||||||
|
const { data: release } = await github.rest.repos.getReleaseByTag({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
tag,
|
||||||
|
});
|
||||||
parameters = {
|
parameters = {
|
||||||
event: "release",
|
event: "release",
|
||||||
name: context.payload.workflow_run.head_branch,
|
name: tag,
|
||||||
|
prerelease: release.prerelease,
|
||||||
shouldDeploy: !isFork
|
shouldDeploy: !isFork
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -119,19 +126,19 @@ jobs:
|
|||||||
if: ${{ fromJson(needs.checks.outputs.artifact).found && fromJson(needs.checks.outputs.parameters).shouldDeploy }}
|
if: ${{ fromJson(needs.checks.outputs.artifact).found && fromJson(needs.checks.outputs.parameters).shouldDeploy }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@01a4d354b70f99a6baf4a1b72827f6d4922e4978 # use-mise-action-v2.0.0
|
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
|
||||||
with:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -146,6 +153,7 @@ jobs:
|
|||||||
const parameters = JSON.parse(process.env.PARAM_JSON);
|
const parameters = JSON.parse(process.env.PARAM_JSON);
|
||||||
core.setOutput("event", parameters.event);
|
core.setOutput("event", parameters.event);
|
||||||
core.setOutput("name", parameters.name);
|
core.setOutput("name", parameters.name);
|
||||||
|
core.setOutput("prerelease", parameters.prerelease);
|
||||||
core.setOutput("shouldDeploy", parameters.shouldDeploy);
|
core.setOutput("shouldDeploy", parameters.shouldDeploy);
|
||||||
|
|
||||||
- name: Download artifact
|
- name: Download artifact
|
||||||
@@ -203,7 +211,7 @@ jobs:
|
|||||||
run: mise run //docs:deploy
|
run: mise run //docs:deploy
|
||||||
|
|
||||||
- name: Deploy Docs Release Domain
|
- name: Deploy Docs Release Domain
|
||||||
if: ${{ steps.parameters.outputs.event == 'release' }}
|
if: ${{ steps.parameters.outputs.event == 'release' && steps.parameters.outputs.prerelease != 'true' }}
|
||||||
env:
|
env:
|
||||||
TF_VAR_prefix_name: ${{ steps.parameters.outputs.name}}
|
TF_VAR_prefix_name: ${{ steps.parameters.outputs.name}}
|
||||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
@@ -213,12 +221,11 @@ jobs:
|
|||||||
run: 'mise run //deployment:tf apply'
|
run: 'mise run //deployment:tf apply'
|
||||||
|
|
||||||
- name: Comment
|
- name: Comment
|
||||||
uses: actions-cool/maintain-one-comment@909842216bc8e8658364c572ec52100f4c2cc50a # v3.3.0
|
uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0
|
||||||
if: ${{ steps.parameters.outputs.event == 'pr' }}
|
if: ${{ steps.parameters.outputs.event == 'pr' }}
|
||||||
with:
|
with:
|
||||||
|
id: docs-pr-url
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
number: ${{ fromJson(needs.checks.outputs.parameters).pr_number }}
|
number: ${{ fromJson(needs.checks.outputs.parameters).pr_number }}
|
||||||
body: |
|
body: |
|
||||||
📖 Documentation deployed to [${{ steps.docs-output.outputs.subdomain }}](https://${{ steps.docs-output.outputs.subdomain }})
|
📖 Documentation deployed to [${{ steps.docs-output.outputs.subdomain }}](https://${{ steps.docs-output.outputs.subdomain }})
|
||||||
emojis: 'rocket'
|
|
||||||
body-include: '<!-- Docs PR URL -->'
|
|
||||||
|
|||||||
@@ -17,19 +17,19 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@01a4d354b70f99a6baf4a1b72827f6d4922e4978 # use-mise-action-v2.0.0
|
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
|
||||||
with:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -44,9 +44,8 @@ jobs:
|
|||||||
run: 'mise run //deployment:tf destroy -- -refresh=false'
|
run: 'mise run //deployment:tf destroy -- -refresh=false'
|
||||||
|
|
||||||
- name: Comment
|
- name: Comment
|
||||||
uses: actions-cool/maintain-one-comment@909842216bc8e8658364c572ec52100f4c2cc50a # v3.3.0
|
uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0
|
||||||
with:
|
with:
|
||||||
|
id: docs-pr-url
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
number: ${{ github.event.number }}
|
|
||||||
delete: true
|
delete: true
|
||||||
body-include: '<!-- Docs PR URL -->'
|
|
||||||
|
|||||||
@@ -14,29 +14,23 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Generate a token
|
- id: token
|
||||||
id: generate-token
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
|
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: 'Checkout'
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.ref }}
|
ref: ${{ github.event.pull_request.head.ref }}
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
|
||||||
persist-credentials: true
|
persist-credentials: true
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup Mise
|
||||||
uses: pnpm/action-setup@26f6d4f2c533a43e6b5da0b4a5dd983f98f7b49a # v6.0.4
|
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
|
||||||
|
|
||||||
- name: Setup Node
|
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
with:
|
||||||
node-version-file: './server/.nvmrc'
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
cache: 'pnpm'
|
|
||||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
|
||||||
|
|
||||||
- name: Fix formatting
|
- name: Fix formatting
|
||||||
run: pnpm --recursive install && pnpm run --recursive --if-present --parallel format:fix
|
run: pnpm --recursive install && pnpm run --recursive --if-present --parallel format:fix
|
||||||
@@ -51,7 +45,7 @@ jobs:
|
|||||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.generate-token.outputs.token }}
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
script: |
|
script: |
|
||||||
github.rest.issues.removeLabel({
|
github.rest.issues.removeLabel({
|
||||||
issue_number: context.payload.pull_request.number,
|
issue_number: context.payload.pull_request.number,
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
- name: Generate a token
|
- name: Generate a token
|
||||||
id: generate_token
|
id: generate_token
|
||||||
if: ${{ inputs.skip != true }}
|
if: ${{ inputs.skip != true }}
|
||||||
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
|
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|||||||
@@ -13,3 +13,4 @@ jobs:
|
|||||||
actions: read
|
actions: read
|
||||||
contents: read
|
contents: read
|
||||||
security-events: write
|
security-events: write
|
||||||
|
secrets: inherit
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
|
- uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6.1.0
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ steps.token.outputs.token }}
|
repo-token: ${{ steps.token.outputs.token }}
|
||||||
|
|||||||
@@ -10,9 +10,13 @@ on:
|
|||||||
type: choice
|
type: choice
|
||||||
options:
|
options:
|
||||||
- 'false'
|
- 'false'
|
||||||
- major
|
|
||||||
- minor
|
- minor
|
||||||
- patch
|
- patch
|
||||||
|
- premajor
|
||||||
|
- preminor
|
||||||
|
- prepatch
|
||||||
|
- prerelease
|
||||||
|
- release
|
||||||
mobileBump:
|
mobileBump:
|
||||||
description: 'Bump mobile build number'
|
description: 'Bump mobile build number'
|
||||||
required: false
|
required: false
|
||||||
@@ -46,43 +50,45 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
ref: ${{ steps.push-tag.outputs.commit_long_sha }}
|
ref: ${{ steps.push-tag.outputs.commit_long_sha }}
|
||||||
version: ${{ steps.output.outputs.version }}
|
version: ${{ steps.output.outputs.version }}
|
||||||
|
rc: ${{ steps.output.outputs.rc }}
|
||||||
permissions: {} # No job-level permissions are needed because it uses the app-token
|
permissions: {} # No job-level permissions are needed because it uses the app-token
|
||||||
steps:
|
steps:
|
||||||
- name: Generate a token
|
- id: token
|
||||||
id: generate-token
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
|
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
persist-credentials: true
|
persist-credentials: true
|
||||||
ref: main
|
ref: main
|
||||||
|
|
||||||
- name: Install uv
|
- name: Setup Mise
|
||||||
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
|
||||||
|
|
||||||
- name: Setup pnpm
|
|
||||||
uses: pnpm/action-setup@26f6d4f2c533a43e6b5da0b4a5dd983f98f7b49a # v6.0.4
|
|
||||||
|
|
||||||
- name: Setup Node
|
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
with:
|
||||||
node-version-file: './server/.nvmrc'
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
cache: 'pnpm'
|
|
||||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
# TODO move to mise
|
||||||
|
- name: Install uv
|
||||||
|
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||||
|
|
||||||
- name: Bump version
|
- name: Bump version
|
||||||
env:
|
env:
|
||||||
SERVER_BUMP: ${{ inputs.serverBump }}
|
SERVER_BUMP: ${{ inputs.serverBump }}
|
||||||
MOBILE_BUMP: ${{ inputs.mobileBump }}
|
MOBILE_BUMP: ${{ inputs.mobileBump }}
|
||||||
run: misc/release/pump-version.sh -s "${SERVER_BUMP}" -m "${MOBILE_BUMP}"
|
run: pnpm --silent release -s "${SERVER_BUMP}" -m "${MOBILE_BUMP}"
|
||||||
|
|
||||||
- id: output
|
- id: output
|
||||||
run: echo "version=$IMMICH_VERSION" >> $GITHUB_OUTPUT
|
run: |
|
||||||
|
echo "version=$IMMICH_VERSION" >> $GITHUB_OUTPUT
|
||||||
|
if [[ "$IMMICH_VERSION" =~ -rc\.[0-9]+$ ]]; then
|
||||||
|
echo "rc=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "rc=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Commit and tag
|
- name: Commit and tag
|
||||||
id: push-tag
|
id: push-tag
|
||||||
@@ -98,6 +104,7 @@ jobs:
|
|||||||
needs: bump_version
|
needs: bump_version
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
secrets:
|
secrets:
|
||||||
KEY_JKS: ${{ secrets.KEY_JKS }}
|
KEY_JKS: ${{ secrets.KEY_JKS }}
|
||||||
ALIAS: ${{ secrets.ALIAS }}
|
ALIAS: ${{ secrets.ALIAS }}
|
||||||
@@ -124,13 +131,13 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Generate a token
|
- name: Generate a token
|
||||||
id: generate-token
|
id: generate-token
|
||||||
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
|
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
@@ -142,9 +149,10 @@ jobs:
|
|||||||
github-token: ${{ steps.generate-token.outputs.token }}
|
github-token: ${{ steps.generate-token.outputs.token }}
|
||||||
|
|
||||||
- name: Create draft release
|
- name: Create draft release
|
||||||
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2
|
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
|
||||||
with:
|
with:
|
||||||
draft: true
|
draft: true
|
||||||
|
prerelease: ${{ needs.bump_version.outputs.rc }}
|
||||||
tag_name: ${{ needs.bump_version.outputs.version }}
|
tag_name: ${{ needs.bump_version.outputs.version }}
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
|
|||||||
@@ -14,16 +14,16 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: mshick/add-pr-comment@8e4927817251f1ff60c001f04568532b38e0b4a0 # v3.11.0
|
- uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.token.outputs.token }}
|
id: preview-status
|
||||||
message-id: 'preview-status'
|
token: ${{ steps.token.outputs.token }}
|
||||||
message: 'Deploying preview environment to https://pr-${{ github.event.pull_request.number }}.preview.internal.immich.build/'
|
body: 'Deploying preview environment to https://pr-${{ github.event.pull_request.number }}.preview.internal.immich.build/'
|
||||||
|
|
||||||
remove-label:
|
remove-label:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -32,7 +32,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
@@ -48,16 +48,16 @@ jobs:
|
|||||||
name: 'preview'
|
name: 'preview'
|
||||||
})
|
})
|
||||||
|
|
||||||
- uses: mshick/add-pr-comment@8e4927817251f1ff60c001f04568532b38e0b4a0 # v3.11.0
|
- uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0
|
||||||
if: ${{ github.event.pull_request.head.repo.fork }}
|
if: ${{ github.event.pull_request.head.repo.fork }}
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.token.outputs.token }}
|
id: preview-status
|
||||||
message-id: 'preview-status'
|
token: ${{ steps.token.outputs.token }}
|
||||||
message: 'PRs from forks cannot have preview environments.'
|
body: 'PRs from forks cannot have preview environments.'
|
||||||
|
|
||||||
- uses: mshick/add-pr-comment@8e4927817251f1ff60c001f04568532b38e0b4a0 # v3.11.0
|
- uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0
|
||||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.token.outputs.token }}
|
id: preview-status
|
||||||
message-id: 'preview-status'
|
token: ${{ steps.token.outputs.token }}
|
||||||
message: 'Preview environment has been removed.'
|
body: 'Preview environment has been removed.'
|
||||||
|
|||||||
+14
-17
@@ -14,34 +14,31 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
id-token: write
|
id-token: write
|
||||||
packages: write
|
packages: write
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: ./open-api/typescript-sdk
|
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup Mise
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
|
||||||
|
|
||||||
# Setup .npmrc file to publish to npm
|
|
||||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
with:
|
||||||
node-version-file: './open-api/typescript-sdk/.nvmrc'
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
registry-url: 'https://registry.npmjs.org'
|
|
||||||
cache: 'pnpm'
|
|
||||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
|
||||||
- name: Install deps
|
- name: Install deps
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm --filter @immich/sdk install --frozen-lockfile
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: pnpm build
|
run: pnpm --filter @immich/sdk build
|
||||||
|
|
||||||
- name: Publish
|
- name: Publish
|
||||||
run: pnpm publish --provenance --no-git-checks
|
env:
|
||||||
|
NPM_TAG: ${{ github.event.release.prerelease && 'rc' || 'latest' }}
|
||||||
|
run: pnpm --filter @immich/sdk publish --provenance --no-git-checks --tag "$NPM_TAG"
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
@@ -49,49 +49,38 @@ jobs:
|
|||||||
working-directory: ./mobile
|
working-directory: ./mobile
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Flutter SDK
|
- name: Setup Mise
|
||||||
uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0
|
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
|
||||||
with:
|
with:
|
||||||
channel: 'stable'
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
flutter-version-file: ./mobile/pubspec.yaml
|
working_directory: ./mobile
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: dart pub get
|
run: flutter pub get
|
||||||
|
|
||||||
- name: Install dependencies for UI package
|
- name: Install dependencies for UI package
|
||||||
run: dart pub get
|
run: flutter pub get
|
||||||
working-directory: ./mobile/packages/ui
|
working-directory: ./mobile/packages/ui
|
||||||
|
|
||||||
- name: Install dependencies for UI Showcase
|
- name: Generate translation files
|
||||||
run: dart pub get
|
run: mise //mobile:codegen:translation
|
||||||
working-directory: ./mobile/packages/ui/showcase
|
|
||||||
|
|
||||||
- name: Install DCM
|
|
||||||
uses: CQLabs/setup-dcm@8697ae0790c0852e964a6ef1d768d62a6675481a # v2.0.1
|
|
||||||
with:
|
|
||||||
github-token: ${{ steps.token.outputs.token }}
|
|
||||||
version: auto
|
|
||||||
working-directory: ./mobile
|
|
||||||
|
|
||||||
- name: Generate translation file
|
|
||||||
run: dart run easy_localization:generate -S ../i18n && dart run bin/generate_keys.dart
|
|
||||||
|
|
||||||
- name: Run Build Runner
|
- name: Run Build Runner
|
||||||
run: make build
|
run: mise //mobile:codegen:dart
|
||||||
|
|
||||||
- name: Generate platform API
|
- name: Generate platform API
|
||||||
run: make pigeon
|
run: mise //mobile:codegen:pigeon
|
||||||
|
|
||||||
- name: Find file changes
|
- name: Find file changes
|
||||||
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
|
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
|
||||||
@@ -101,26 +90,24 @@ jobs:
|
|||||||
mobile/**/*.g.dart
|
mobile/**/*.g.dart
|
||||||
mobile/**/*.gr.dart
|
mobile/**/*.gr.dart
|
||||||
mobile/**/*.drift.dart
|
mobile/**/*.drift.dart
|
||||||
|
mobile/**/*.g.swift
|
||||||
|
mobile/**/*.g.kt
|
||||||
|
|
||||||
- name: Verify files have not changed
|
- name: Verify files have not changed
|
||||||
if: steps.verify-changed-files.outputs.files_changed == 'true'
|
if: steps.verify-changed-files.outputs.files_changed == 'true'
|
||||||
env:
|
env:
|
||||||
CHANGED_FILES: ${{ steps.verify-changed-files.outputs.changed_files }}
|
CHANGED_FILES: ${{ steps.verify-changed-files.outputs.changed_files }}
|
||||||
run: |
|
run: |
|
||||||
echo "ERROR: Generated files not up to date! Run 'make build' and 'make pigeon' inside the mobile directory"
|
echo "ERROR: Generated files not up to date! Run 'mise //mobile:codegen:dart' and 'mise //mobile:codegen:pigeon'"
|
||||||
echo "Changed files: ${CHANGED_FILES}"
|
echo "Changed files: ${CHANGED_FILES}"
|
||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
- name: Run dart analyze
|
- name: Run analyze
|
||||||
run: dart analyze --fatal-infos
|
run: mise //mobile:analyze
|
||||||
|
|
||||||
- name: Run dart format
|
- name: Run format
|
||||||
run: make format
|
run: mise //mobile:format
|
||||||
|
|
||||||
# TODO: Re-enable after upgrading custom_lint
|
# TODO: Re-enable after upgrading custom_lint
|
||||||
# - name: Run dart custom_lint
|
# - name: Run dart custom_lint
|
||||||
# run: dart run custom_lint
|
# run: dart run custom_lint
|
||||||
|
|
||||||
# TODO: Use https://github.com/CQLabs/dcm-action
|
|
||||||
- name: Run DCM
|
|
||||||
run: dcm analyze lib --fatal-style --fatal-warnings
|
|
||||||
|
|||||||
+241
-248
@@ -17,7 +17,7 @@ jobs:
|
|||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
@@ -28,33 +28,72 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
github-token: ${{ steps.token.outputs.token }}
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
filters: |
|
filters: |
|
||||||
|
root:
|
||||||
|
- 'misc/**'
|
||||||
|
- 'pnpm-lock.yaml'
|
||||||
|
- 'mise.toml'
|
||||||
i18n:
|
i18n:
|
||||||
- 'i18n/**'
|
- 'i18n/**'
|
||||||
|
- 'mise.toml'
|
||||||
web:
|
web:
|
||||||
- 'web/**'
|
- 'web/**'
|
||||||
- 'i18n/**'
|
- 'i18n/**'
|
||||||
- 'open-api/typescript-sdk/**'
|
- 'packages/sdk/**'
|
||||||
- 'pnpm-lock.yaml'
|
- 'pnpm-lock.yaml'
|
||||||
|
- 'mise.toml'
|
||||||
server:
|
server:
|
||||||
- 'server/**'
|
- 'server/**'
|
||||||
- 'pnpm-lock.yaml'
|
- 'pnpm-lock.yaml'
|
||||||
|
- 'mise.toml'
|
||||||
cli:
|
cli:
|
||||||
- 'cli/**'
|
- 'packages/cli/**'
|
||||||
- 'open-api/typescript-sdk/**'
|
- 'packages/sdk/**'
|
||||||
- 'pnpm-lock.yaml'
|
- 'pnpm-lock.yaml'
|
||||||
|
- 'mise.toml'
|
||||||
e2e:
|
e2e:
|
||||||
- 'e2e/**'
|
- 'e2e/**'
|
||||||
- 'pnpm-lock.yaml'
|
- 'pnpm-lock.yaml'
|
||||||
|
- 'mise.toml'
|
||||||
mobile:
|
mobile:
|
||||||
- 'mobile/**'
|
- 'mobile/**'
|
||||||
|
- 'mise.toml'
|
||||||
machine-learning:
|
machine-learning:
|
||||||
- 'machine-learning/**'
|
- 'machine-learning/**'
|
||||||
|
- 'mise.toml'
|
||||||
.github:
|
.github:
|
||||||
- '.github/**'
|
- '.github/**'
|
||||||
force-filters: |
|
force-filters: |
|
||||||
- '.github/workflows/test.yml'
|
- '.github/workflows/test.yml'
|
||||||
force-events: 'workflow_dispatch'
|
force-events: 'workflow_dispatch'
|
||||||
|
|
||||||
|
root-unit-tests:
|
||||||
|
name: Test the root workspace
|
||||||
|
needs: pre-job
|
||||||
|
if: ${{ fromJSON(needs.pre-job.outputs.should_run).root == true }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
|
with:
|
||||||
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
- name: Setup Mise
|
||||||
|
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
|
||||||
|
with:
|
||||||
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
- name: Run unit tests
|
||||||
|
run: pnpm test
|
||||||
|
|
||||||
server-unit-tests:
|
server-unit-tests:
|
||||||
name: Test & Lint Server
|
name: Test & Lint Server
|
||||||
needs: pre-job
|
needs: pre-job
|
||||||
@@ -62,44 +101,27 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: ./server
|
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup Mise
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
|
||||||
- name: Setup Node
|
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
with:
|
||||||
node-version-file: './server/.nvmrc'
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
cache: 'pnpm'
|
|
||||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
- name: Run ci-unit
|
||||||
- name: Run package manager install
|
run: mise run //server:ci-unit
|
||||||
run: pnpm install
|
|
||||||
- name: Run linter
|
|
||||||
run: pnpm lint
|
|
||||||
if: ${{ !cancelled() }}
|
|
||||||
- name: Run formatter
|
|
||||||
run: pnpm format
|
|
||||||
if: ${{ !cancelled() }}
|
|
||||||
- name: Run tsc
|
|
||||||
run: pnpm check
|
|
||||||
if: ${{ !cancelled() }}
|
|
||||||
- name: Run small tests & coverage
|
|
||||||
run: pnpm test
|
|
||||||
if: ${{ !cancelled() }}
|
|
||||||
cli-unit-tests:
|
cli-unit-tests:
|
||||||
name: Unit Test CLI
|
name: Unit Test CLI
|
||||||
needs: pre-job
|
needs: pre-job
|
||||||
@@ -109,44 +131,28 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: ./cli
|
working-directory: ./packages/cli
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
- name: Setup Mise
|
||||||
- name: Setup Node
|
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
with:
|
||||||
node-version-file: './cli/.nvmrc'
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
cache: 'pnpm'
|
|
||||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
- name: Run ci-unit
|
||||||
- name: Setup typescript-sdk
|
run: mise run ci-unit
|
||||||
run: pnpm install && pnpm run build
|
|
||||||
working-directory: ./open-api/typescript-sdk
|
|
||||||
- name: Install deps
|
|
||||||
run: pnpm install
|
|
||||||
- name: Run linter
|
|
||||||
run: pnpm lint
|
|
||||||
if: ${{ !cancelled() }}
|
|
||||||
- name: Run formatter
|
|
||||||
run: pnpm format
|
|
||||||
if: ${{ !cancelled() }}
|
|
||||||
- name: Run tsc
|
|
||||||
run: pnpm check
|
|
||||||
if: ${{ !cancelled() }}
|
|
||||||
- name: Run unit tests & coverage
|
|
||||||
run: pnpm test
|
|
||||||
if: ${{ !cancelled() }}
|
|
||||||
cli-unit-tests-win:
|
cli-unit-tests-win:
|
||||||
name: Unit Test CLI (Windows)
|
name: Unit Test CLI (Windows)
|
||||||
needs: pre-job
|
needs: pre-job
|
||||||
@@ -156,39 +162,41 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: ./cli
|
working-directory: ./packages/cli
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
- name: Setup Mise
|
||||||
- name: Setup Node
|
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
with:
|
||||||
node-version-file: './cli/.nvmrc'
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
cache: 'pnpm'
|
|
||||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
- name: Run setup @immich/sdk
|
||||||
- name: Setup typescript-sdk
|
run: mise run //:sdk:install && mise run //:sdk:build
|
||||||
run: pnpm install --frozen-lockfile && pnpm build
|
|
||||||
working-directory: ./open-api/typescript-sdk
|
- name: Run pnpm install
|
||||||
- name: Install deps
|
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
# Skip linter & formatter in Windows test.
|
# Skip linter & formatter in Windows test.
|
||||||
|
|
||||||
- name: Run tsc
|
- name: Run tsc
|
||||||
run: pnpm check
|
run: pnpm check
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
|
||||||
- name: Run unit tests & coverage
|
- name: Run unit tests & coverage
|
||||||
run: pnpm test
|
run: pnpm test
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
|
||||||
web-lint:
|
web-lint:
|
||||||
name: Lint Web
|
name: Lint Web
|
||||||
needs: pre-job
|
needs: pre-job
|
||||||
@@ -201,38 +209,32 @@ jobs:
|
|||||||
working-directory: ./web
|
working-directory: ./web
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
- name: Setup Mise
|
||||||
- name: Setup Node
|
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
with:
|
||||||
node-version-file: './web/.nvmrc'
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
cache: 'pnpm'
|
|
||||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
- name: Run setup @immich/sdk
|
||||||
- name: Run setup typescript-sdk
|
run: mise run //:sdk:install && mise run //:sdk:build
|
||||||
run: pnpm install --frozen-lockfile && pnpm build
|
|
||||||
working-directory: ./open-api/typescript-sdk
|
|
||||||
- name: Run pnpm install
|
- name: Run pnpm install
|
||||||
run: pnpm rebuild && pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Run linter
|
- name: Run linter
|
||||||
run: pnpm lint
|
run: pnpm lint
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
- name: Run formatter
|
|
||||||
run: pnpm format
|
|
||||||
if: ${{ !cancelled() }}
|
|
||||||
- name: Run svelte checks
|
|
||||||
run: pnpm check:svelte
|
|
||||||
if: ${{ !cancelled() }}
|
|
||||||
web-unit-tests:
|
web-unit-tests:
|
||||||
name: Test Web
|
name: Test Web
|
||||||
needs: pre-job
|
needs: pre-job
|
||||||
@@ -245,35 +247,25 @@ jobs:
|
|||||||
working-directory: ./web
|
working-directory: ./web
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
- name: Setup Mise
|
||||||
- name: Setup Node
|
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
with:
|
||||||
node-version-file: './web/.nvmrc'
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
cache: 'pnpm'
|
|
||||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
- name: Run ci-unit
|
||||||
- name: Run setup typescript-sdk
|
run: mise run ci-unit
|
||||||
run: pnpm install --frozen-lockfile && pnpm build
|
|
||||||
working-directory: ./open-api/typescript-sdk
|
|
||||||
- name: Run npm install
|
|
||||||
run: pnpm install --frozen-lockfile
|
|
||||||
- name: Run tsc
|
|
||||||
run: pnpm check:typescript
|
|
||||||
if: ${{ !cancelled() }}
|
|
||||||
- name: Run unit tests & coverage
|
|
||||||
run: pnpm test
|
|
||||||
if: ${{ !cancelled() }}
|
|
||||||
i18n-tests:
|
i18n-tests:
|
||||||
name: Test i18n
|
name: Test i18n
|
||||||
needs: pre-job
|
needs: pre-job
|
||||||
@@ -283,34 +275,35 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
- name: Setup Mise
|
||||||
- name: Setup Node
|
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
with:
|
||||||
node-version-file: './web/.nvmrc'
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
cache: 'pnpm'
|
|
||||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm --filter=immich-i18n install --frozen-lockfile
|
run: pnpm -w install --frozen-lockfile
|
||||||
|
|
||||||
- name: Format
|
- name: Format
|
||||||
run: pnpm --filter=immich-i18n format:fix
|
run: pnpm format:fix
|
||||||
|
|
||||||
- name: Find file changes
|
- name: Find file changes
|
||||||
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
|
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
|
||||||
id: verify-changed-files
|
id: verify-changed-files
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
i18n/**
|
i18n/**
|
||||||
|
|
||||||
- name: Verify files have not changed
|
- name: Verify files have not changed
|
||||||
if: steps.verify-changed-files.outputs.files_changed == 'true'
|
if: steps.verify-changed-files.outputs.files_changed == 'true'
|
||||||
env:
|
env:
|
||||||
@@ -319,6 +312,7 @@ jobs:
|
|||||||
echo "ERROR: i18n files not up to date!"
|
echo "ERROR: i18n files not up to date!"
|
||||||
echo "Changed files: ${CHANGED_FILES}"
|
echo "Changed files: ${CHANGED_FILES}"
|
||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
e2e-tests-lint:
|
e2e-tests-lint:
|
||||||
name: End-to-End Lint
|
name: End-to-End Lint
|
||||||
needs: pre-job
|
needs: pre-job
|
||||||
@@ -331,40 +325,26 @@ jobs:
|
|||||||
working-directory: ./e2e
|
working-directory: ./e2e
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
- name: Setup Mise
|
||||||
- name: Setup Node
|
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
with:
|
||||||
node-version-file: './e2e/.nvmrc'
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
cache: 'pnpm'
|
|
||||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
- name: Run ci-unit
|
||||||
- name: Run setup typescript-sdk
|
run: mise run ci-unit
|
||||||
run: pnpm install --frozen-lockfile && pnpm build
|
|
||||||
working-directory: ./open-api/typescript-sdk
|
|
||||||
if: ${{ !cancelled() }}
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install --frozen-lockfile
|
|
||||||
if: ${{ !cancelled() }}
|
|
||||||
- name: Run linter
|
|
||||||
run: pnpm lint
|
|
||||||
if: ${{ !cancelled() }}
|
|
||||||
- name: Run formatter
|
|
||||||
run: pnpm format
|
|
||||||
if: ${{ !cancelled() }}
|
|
||||||
- name: Run tsc
|
|
||||||
run: pnpm check
|
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
|
||||||
server-medium-tests:
|
server-medium-tests:
|
||||||
name: Medium Tests (Server)
|
name: Medium Tests (Server)
|
||||||
needs: pre-job
|
needs: pre-job
|
||||||
@@ -377,34 +357,27 @@ jobs:
|
|||||||
working-directory: ./server
|
working-directory: ./server
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
|
||||||
- name: Setup Node
|
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
|
||||||
node-version-file: './server/.nvmrc'
|
|
||||||
cache: 'pnpm'
|
|
||||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@01a4d354b70f99a6baf4a1b72827f6d4922e4978 # use-mise-action-v2.0.0
|
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
|
||||||
with:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
- name: Run pnpm install
|
|
||||||
run: SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm install --frozen-lockfile
|
- name: Run ci-medium
|
||||||
- name: Run medium tests
|
run: mise run ci-medium
|
||||||
run: pnpm test:medium
|
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
|
||||||
e2e-tests-server-cli:
|
e2e-tests-server-cli:
|
||||||
name: End-to-End Tests (Server & CLI)
|
name: End-to-End Tests (Server & CLI)
|
||||||
needs: pre-job
|
needs: pre-job
|
||||||
@@ -420,63 +393,68 @@ jobs:
|
|||||||
runner: [ubuntu-latest, ubuntu-24.04-arm]
|
runner: [ubuntu-latest, ubuntu-24.04-arm]
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './e2e/.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||||
- name: Run setup typescript-sdk
|
|
||||||
run: pnpm install --frozen-lockfile && pnpm build
|
- name: Setup packages
|
||||||
working-directory: ./open-api/typescript-sdk
|
run: pnpm --filter @immich/sdk --filter @immich/cli install --frozen-lockfile && pnpm --filter @immich/sdk --filter @immich/cli build
|
||||||
if: ${{ !cancelled() }}
|
|
||||||
- name: Run setup web
|
- name: Run setup web
|
||||||
run: pnpm install --frozen-lockfile && pnpm exec svelte-kit sync
|
run: pnpm install --frozen-lockfile && pnpm exec svelte-kit sync
|
||||||
working-directory: ./web
|
working-directory: ./web
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
- name: Run setup cli
|
|
||||||
run: pnpm install --frozen-lockfile && pnpm build
|
|
||||||
working-directory: ./cli
|
|
||||||
if: ${{ !cancelled() }}
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
|
||||||
- name: Start Docker Compose
|
- name: Start Docker Compose
|
||||||
run: docker compose up -d --build --renew-anon-volumes --force-recreate --remove-orphans --wait --wait-timeout 300
|
run: docker compose up -d --build --renew-anon-volumes --force-recreate --remove-orphans --wait --wait-timeout 300
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
|
||||||
- name: Run e2e tests (api & cli)
|
- name: Run e2e tests (api & cli)
|
||||||
env:
|
env:
|
||||||
VITEST_DISABLE_DOCKER_SETUP: true
|
VITEST_DISABLE_DOCKER_SETUP: true
|
||||||
run: pnpm test
|
run: pnpm test
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
|
||||||
- name: Run e2e tests (maintenance)
|
- name: Run e2e tests (maintenance)
|
||||||
env:
|
env:
|
||||||
VITEST_DISABLE_DOCKER_SETUP: true
|
VITEST_DISABLE_DOCKER_SETUP: true
|
||||||
run: pnpm test:maintenance
|
run: pnpm test:maintenance
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
|
||||||
- name: Capture Docker logs
|
- name: Capture Docker logs
|
||||||
if: always()
|
if: always()
|
||||||
run: docker compose logs --no-color > docker-compose-logs.txt
|
run: docker compose logs --no-color > docker-compose-logs.txt
|
||||||
working-directory: ./e2e
|
working-directory: ./e2e
|
||||||
|
|
||||||
- name: Archive Docker logs
|
- name: Archive Docker logs
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: e2e-server-docker-logs-${{ matrix.runner }}
|
name: e2e-server-docker-logs-${{ matrix.runner }}
|
||||||
path: e2e/docker-compose-logs.txt
|
path: e2e/docker-compose-logs.txt
|
||||||
|
|
||||||
e2e-tests-web:
|
e2e-tests-web:
|
||||||
name: End-to-End Tests (Web)
|
name: End-to-End Tests (Web)
|
||||||
needs: pre-job
|
needs: pre-job
|
||||||
@@ -492,81 +470,95 @@ jobs:
|
|||||||
runner: [ubuntu-latest, ubuntu-24.04-arm]
|
runner: [ubuntu-latest, ubuntu-24.04-arm]
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './e2e/.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||||
- name: Run setup typescript-sdk
|
|
||||||
run: pnpm install --frozen-lockfile && pnpm build
|
- name: Run setup @immich/sdk
|
||||||
working-directory: ./open-api/typescript-sdk
|
run: pnpm --filter @immich/sdk install --frozen-lockfile && pnpm --filter @immich/sdk build
|
||||||
|
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
|
||||||
- name: Install Playwright Browsers
|
- name: Install Playwright Browsers
|
||||||
run: pnpm exec playwright install chromium --only-shell
|
run: pnpm exec playwright install chromium --only-shell
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
|
||||||
- name: Docker build
|
- name: Docker build
|
||||||
run: docker compose up -d --build --renew-anon-volumes --force-recreate --remove-orphans --wait --wait-timeout 300
|
run: docker compose up -d --build --renew-anon-volumes --force-recreate --remove-orphans --wait --wait-timeout 300
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
|
||||||
- name: Run e2e tests (web)
|
- name: Run e2e tests (web)
|
||||||
env:
|
env:
|
||||||
PLAYWRIGHT_DISABLE_WEBSERVER: true
|
PLAYWRIGHT_DISABLE_WEBSERVER: true
|
||||||
run: pnpm test:web
|
run: pnpm test:web
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
|
||||||
- name: Archive e2e test (web) results
|
- name: Archive e2e test (web) results
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
if: success() || failure()
|
if: success() || failure()
|
||||||
with:
|
with:
|
||||||
name: e2e-web-test-results-${{ matrix.runner }}
|
name: e2e-web-test-results-${{ matrix.runner }}
|
||||||
path: e2e/playwright-report/
|
path: e2e/playwright-report/
|
||||||
|
|
||||||
- name: Run ui tests (web)
|
- name: Run ui tests (web)
|
||||||
env:
|
env:
|
||||||
PLAYWRIGHT_DISABLE_WEBSERVER: true
|
PLAYWRIGHT_DISABLE_WEBSERVER: true
|
||||||
run: pnpm test:web:ui
|
run: pnpm test:web:ui
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
|
||||||
- name: Archive ui test (web) results
|
- name: Archive ui test (web) results
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
if: success() || failure()
|
if: success() || failure()
|
||||||
with:
|
with:
|
||||||
name: e2e-ui-test-results-${{ matrix.runner }}
|
name: e2e-ui-test-results-${{ matrix.runner }}
|
||||||
path: e2e/playwright-report/
|
path: e2e/playwright-report/
|
||||||
|
|
||||||
- name: Run maintenance tests
|
- name: Run maintenance tests
|
||||||
env:
|
env:
|
||||||
PLAYWRIGHT_DISABLE_WEBSERVER: true
|
PLAYWRIGHT_DISABLE_WEBSERVER: true
|
||||||
run: pnpm test:web:maintenance
|
run: pnpm test:web:maintenance
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
|
||||||
- name: Archive maintenance tests (web) results
|
- name: Archive maintenance tests (web) results
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
if: success() || failure()
|
if: success() || failure()
|
||||||
with:
|
with:
|
||||||
name: e2e-maintenance-isolated-test-results-${{ matrix.runner }}
|
name: e2e-maintenance-isolated-test-results-${{ matrix.runner }}
|
||||||
path: e2e/playwright-report/
|
path: e2e/playwright-report/
|
||||||
|
|
||||||
- name: Capture Docker logs
|
- name: Capture Docker logs
|
||||||
if: always()
|
if: always()
|
||||||
run: docker compose logs --no-color > docker-compose-logs.txt
|
run: docker compose logs --no-color > docker-compose-logs.txt
|
||||||
working-directory: ./e2e
|
working-directory: ./e2e
|
||||||
|
|
||||||
- name: Archive Docker logs
|
- name: Archive Docker logs
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: e2e-web-docker-logs-${{ matrix.runner }}
|
name: e2e-web-docker-logs-${{ matrix.runner }}
|
||||||
path: e2e/docker-compose-logs.txt
|
path: e2e/docker-compose-logs.txt
|
||||||
|
|
||||||
success-check-e2e:
|
success-check-e2e:
|
||||||
name: End-to-End Tests Success
|
name: End-to-End Tests Success
|
||||||
needs: [e2e-tests-server-cli, e2e-tests-web]
|
needs: [e2e-tests-server-cli, e2e-tests-web]
|
||||||
@@ -586,26 +578,32 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup Flutter SDK
|
|
||||||
uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0
|
- name: Setup Mise
|
||||||
|
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
|
||||||
with:
|
with:
|
||||||
channel: 'stable'
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
flutter-version-file: ./mobile/pubspec.yaml
|
working_directory: ./mobile
|
||||||
- name: Generate translation file
|
|
||||||
run: dart run easy_localization:generate -S ../i18n && dart run bin/generate_keys.dart
|
- name: Install dependencies
|
||||||
|
run: flutter pub get
|
||||||
working-directory: ./mobile
|
working-directory: ./mobile
|
||||||
|
|
||||||
|
- name: Generate translation files
|
||||||
|
run: mise //mobile:codegen:translation
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
working-directory: ./mobile
|
run: mise //mobile:test
|
||||||
run: flutter test -j 1
|
|
||||||
ml-unit-tests:
|
ml-unit-tests:
|
||||||
name: Unit Test ML
|
name: Unit Test ML
|
||||||
needs: pre-job
|
needs: pre-job
|
||||||
@@ -613,40 +611,29 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
env:
|
|
||||||
MISE_CEILING_PATHS: ${{ github.workspace }}
|
|
||||||
MISE_TRUSTED_CONFIG_PATHS: ${{ github.workspace }}/machine-learning/mise.toml
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: ./machine-learning
|
working-directory: ./machine-learning
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@01a4d354b70f99a6baf4a1b72827f6d4922e4978 # use-mise-action-v2.0.0
|
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
|
||||||
with:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
working_directory: ./machine-learning
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Run ci-unit
|
||||||
run: mise run install --extra cpu
|
run: mise run ci-unit
|
||||||
- name: Lint code
|
|
||||||
run: mise run lint --output-format=github
|
|
||||||
- name: Format code
|
|
||||||
run: mise run format
|
|
||||||
- name: Run type checking
|
|
||||||
run: mise run check
|
|
||||||
- name: Run tests and coverage
|
|
||||||
run: mise run test
|
|
||||||
github-files-formatting:
|
github-files-formatting:
|
||||||
name: .github Files Formatting
|
name: .github Files Formatting
|
||||||
needs: pre-job
|
needs: pre-job
|
||||||
@@ -659,29 +646,29 @@ jobs:
|
|||||||
working-directory: ./.github
|
working-directory: ./.github
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
- name: Setup Mise
|
||||||
- name: Setup Node
|
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
with:
|
||||||
node-version-file: './.github/.nvmrc'
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
cache: 'pnpm'
|
|
||||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
|
||||||
- name: Run pnpm install
|
- name: Run pnpm install
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Run formatter
|
- name: Run formatter
|
||||||
run: pnpm format
|
run: pnpm format
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
|
||||||
shellcheck:
|
shellcheck:
|
||||||
name: ShellCheck
|
name: ShellCheck
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -689,12 +676,12 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
@@ -710,39 +697,37 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
- name: Setup Mise
|
||||||
- name: Setup Node
|
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
with:
|
||||||
node-version-file: './server/.nvmrc'
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
cache: 'pnpm'
|
|
||||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
|
||||||
- name: Install server dependencies
|
- name: Install server dependencies
|
||||||
run: SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm --filter immich install --frozen-lockfile
|
run: SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm --filter immich install --frozen-lockfile
|
||||||
- name: Build the app
|
|
||||||
run: pnpm --filter immich build
|
|
||||||
- name: Run API generation
|
- name: Run API generation
|
||||||
run: ./bin/generate-open-api.sh
|
run: mise //:open-api
|
||||||
working-directory: open-api
|
working-directory: open-api
|
||||||
|
|
||||||
- name: Find file changes
|
- name: Find file changes
|
||||||
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
|
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
|
||||||
id: verify-changed-files
|
id: verify-changed-files
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
mobile/openapi
|
mobile/openapi
|
||||||
open-api/typescript-sdk
|
packages/sdk
|
||||||
open-api/immich-openapi-specs.json
|
open-api/immich-openapi-specs.json
|
||||||
|
|
||||||
- name: Verify files have not changed
|
- name: Verify files have not changed
|
||||||
if: steps.verify-changed-files.outputs.files_changed == 'true'
|
if: steps.verify-changed-files.outputs.files_changed == 'true'
|
||||||
env:
|
env:
|
||||||
@@ -751,6 +736,7 @@ jobs:
|
|||||||
echo "ERROR: Generated files not up to date!"
|
echo "ERROR: Generated files not up to date!"
|
||||||
echo "Changed files: ${CHANGED_FILES}"
|
echo "Changed files: ${CHANGED_FILES}"
|
||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
sql-schema-up-to-date:
|
sql-schema-up-to-date:
|
||||||
name: SQL Schema Checks
|
name: SQL Schema Checks
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -767,46 +753,50 @@ jobs:
|
|||||||
--health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
--health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
||||||
ports:
|
ports:
|
||||||
- 5432:5432
|
- 5432:5432
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: ./server
|
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
|
||||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
- name: Setup Mise
|
||||||
- name: Setup Node
|
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
with:
|
||||||
node-version-file: './server/.nvmrc'
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
cache: 'pnpm'
|
|
||||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
|
||||||
- name: Install server dependencies
|
- name: Install server dependencies
|
||||||
run: SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm install --frozen-lockfile
|
run: SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build plugins
|
||||||
|
run: mise //:plugins
|
||||||
|
|
||||||
- name: Build the app
|
- name: Build the app
|
||||||
run: pnpm build
|
run: mise //server:build
|
||||||
|
|
||||||
- name: Run existing migrations
|
- name: Run existing migrations
|
||||||
run: pnpm migrations:run
|
run: pnpm --filter immich migrations:run
|
||||||
|
|
||||||
- name: Test npm run schema:reset command works
|
- name: Test npm run schema:reset command works
|
||||||
run: pnpm schema:reset
|
run: pnpm --filter immich schema:reset
|
||||||
|
|
||||||
- name: Generate new migrations
|
- name: Generate new migrations
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: pnpm migrations:generate src/TestMigration
|
run: pnpm --filter migrations:generate src/TestMigration
|
||||||
|
|
||||||
- name: Find file changes
|
- name: Find file changes
|
||||||
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
|
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
|
||||||
id: verify-changed-files
|
id: verify-changed-files
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
server/src
|
server/src
|
||||||
|
|
||||||
- name: Verify migration files have not changed
|
- name: Verify migration files have not changed
|
||||||
if: steps.verify-changed-files.outputs.files_changed == 'true'
|
if: steps.verify-changed-files.outputs.files_changed == 'true'
|
||||||
env:
|
env:
|
||||||
@@ -814,18 +804,21 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "ERROR: Generated migration files not up to date!"
|
echo "ERROR: Generated migration files not up to date!"
|
||||||
echo "Changed files: ${CHANGED_FILES}"
|
echo "Changed files: ${CHANGED_FILES}"
|
||||||
cat ./src/*-TestMigration.ts
|
cat ./server/src/*-TestMigration.ts
|
||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
- name: Run SQL generation
|
- name: Run SQL generation
|
||||||
run: pnpm sync:sql
|
run: mise //:sql
|
||||||
env:
|
env:
|
||||||
DB_URL: postgres://postgres:postgres@localhost:5432/immich
|
DB_URL: postgres://postgres:postgres@localhost:5432/immich
|
||||||
|
|
||||||
- name: Find file changes
|
- name: Find file changes
|
||||||
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
|
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
|
||||||
id: verify-changed-sql-files
|
id: verify-changed-sql-files
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
server/src/queries
|
server/src/queries
|
||||||
|
|
||||||
- name: Verify SQL files have not changed
|
- name: Verify SQL files have not changed
|
||||||
if: steps.verify-changed-sql-files.outputs.files_changed == 'true'
|
if: steps.verify-changed-sql-files.outputs.files_changed == 'true'
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
@@ -36,7 +36,7 @@ jobs:
|
|||||||
github-token: ${{ steps.token.outputs.token }}
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
filters: |
|
filters: |
|
||||||
i18n:
|
i18n:
|
||||||
- modified: 'i18n/!(en|package)**\.json'
|
- modified: 'i18n/!(en)**\.json'
|
||||||
skip-force-logic: 'true'
|
skip-force-logic: 'true'
|
||||||
|
|
||||||
enforce-lock:
|
enforce-lock:
|
||||||
@@ -47,7 +47,7 @@ jobs:
|
|||||||
if: ${{ fromJSON(needs.pre-job.outputs.should_run).i18n == true }}
|
if: ${{ fromJSON(needs.pre-job.outputs.should_run).i18n == true }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ mobile/openapi/doc
|
|||||||
mobile/openapi/.openapi-generator/FILES
|
mobile/openapi/.openapi-generator/FILES
|
||||||
mobile/ios/build
|
mobile/ios/build
|
||||||
|
|
||||||
open-api/typescript-sdk/build
|
packages/**/build
|
||||||
mobile/android/fastlane/report.xml
|
mobile/android/fastlane/report.xml
|
||||||
mobile/ios/fastlane/report.xml
|
mobile/ios/fastlane/report.xml
|
||||||
|
|
||||||
|
|||||||
Vendored
+6
-4
@@ -23,15 +23,17 @@
|
|||||||
"type": "node",
|
"type": "node",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "Immich CLI",
|
"name": "Immich CLI",
|
||||||
"program": "${workspaceFolder}/cli/dist/index.js",
|
"program": "${workspaceFolder}/packages/cli/dist/index.js",
|
||||||
"args": ["upload", "--help"],
|
"args": ["upload", "--help"],
|
||||||
"runtimeArgs": ["--enable-source-maps"],
|
"runtimeArgs": ["--enable-source-maps"],
|
||||||
"console": "integratedTerminal",
|
"console": "integratedTerminal",
|
||||||
"resolveSourceMapLocations": ["${workspaceFolder}/cli/dist/**/*.js.map"],
|
"resolveSourceMapLocations": [
|
||||||
|
"${workspaceFolder}/packages/cli/dist/**/*.js.map"
|
||||||
|
],
|
||||||
"sourceMaps": true,
|
"sourceMaps": true,
|
||||||
"outFiles": ["${workspaceFolder}/cli/dist/**/*.js"],
|
"outFiles": ["${workspaceFolder}/packages/cli/dist/**/*.js"],
|
||||||
"skipFiles": ["<node_internals>/**"],
|
"skipFiles": ["<node_internals>/**"],
|
||||||
"preLaunchTask": "Build Immich CLI"
|
"preLaunchTask": "Build @immich/cli"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1
@@ -60,6 +60,7 @@
|
|||||||
"explorer.fileNesting.patterns": {
|
"explorer.fileNesting.patterns": {
|
||||||
"*.dart": "${capture}.g.dart,${capture}.gr.dart,${capture}.drift.dart",
|
"*.dart": "${capture}.g.dart,${capture}.gr.dart,${capture}.drift.dart",
|
||||||
"*.ts": "${capture}.spec.ts,${capture}.mock.ts",
|
"*.ts": "${capture}.spec.ts,${capture}.mock.ts",
|
||||||
|
"*.js": "${capture}.spec.js,${capture}.mock.js",
|
||||||
"package.json": "package-lock.json, yarn.lock, pnpm-lock.yaml, bun.lockb, bun.lock, pnpm-workspace.yaml, .pnpmfile.cjs"
|
"package.json": "package-lock.json, yarn.lock, pnpm-lock.yaml, bun.lockb, bun.lock, pnpm-workspace.yaml, .pnpmfile.cjs"
|
||||||
},
|
},
|
||||||
"search.exclude": {
|
"search.exclude": {
|
||||||
|
|||||||
+1
-1
@@ -4,4 +4,4 @@
|
|||||||
/web/ @danieldietzler
|
/web/ @danieldietzler
|
||||||
/machine-learning/ @mertalev
|
/machine-learning/ @mertalev
|
||||||
/e2e/ @danieldietzler
|
/e2e/ @danieldietzler
|
||||||
/mobile/ @shenlong-tanwen
|
/mobile/ @shenlong-tanwen @santoshakil
|
||||||
|
|||||||
@@ -1,134 +0,0 @@
|
|||||||
# Contributor Covenant Code of Conduct
|
|
||||||
|
|
||||||
## Our Pledge
|
|
||||||
|
|
||||||
We as members, contributors, and leaders pledge to make participation
|
|
||||||
in our community a harassment-free experience for everyone, regardless
|
|
||||||
of age, body size, visible or invisible disability, ethnicity, sex
|
|
||||||
characteristics, gender identity and expression, level of experience,
|
|
||||||
education, socio-economic status, nationality, personal appearance,
|
|
||||||
race, religion, or sexual identity and orientation.
|
|
||||||
|
|
||||||
We pledge to act and interact in ways that contribute to an open,
|
|
||||||
welcoming, diverse, inclusive, and healthy community.
|
|
||||||
|
|
||||||
## Our Standards
|
|
||||||
|
|
||||||
Examples of behavior that contributes to a positive environment for
|
|
||||||
our community include:
|
|
||||||
|
|
||||||
- Demonstrating empathy and kindness toward other people
|
|
||||||
- Being respectful of differing opinions, viewpoints, and experiences
|
|
||||||
- Giving and gracefully accepting constructive feedback
|
|
||||||
- Accepting responsibility and apologizing to those affected by our
|
|
||||||
mistakes, and learning from the experience
|
|
||||||
- Focusing on what is best not just for us as individuals, but for the
|
|
||||||
overall community
|
|
||||||
|
|
||||||
Examples of unacceptable behavior include:
|
|
||||||
|
|
||||||
- The use of sexualized language or imagery, and sexual attention or
|
|
||||||
advances of any kind
|
|
||||||
- Trolling, insulting or derogatory comments, and personal or
|
|
||||||
political attacks
|
|
||||||
- Public or private harassment
|
|
||||||
- Publishing others' private information, such as a physical or email
|
|
||||||
address, without their explicit permission
|
|
||||||
- Other conduct which could reasonably be considered inappropriate in
|
|
||||||
a professional setting
|
|
||||||
|
|
||||||
## Enforcement Responsibilities
|
|
||||||
|
|
||||||
Community leaders are responsible for clarifying and enforcing our
|
|
||||||
standards of acceptable behavior and will take appropriate and fair
|
|
||||||
corrective action in response to any behavior that they deem
|
|
||||||
inappropriate, threatening, offensive, or harmful.
|
|
||||||
|
|
||||||
Community leaders have the right and responsibility to remove, edit,
|
|
||||||
or reject comments, commits, code, wiki edits, issues, and other
|
|
||||||
contributions that are not aligned to this Code of Conduct, and will
|
|
||||||
communicate reasons for moderation decisions when appropriate.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This Code of Conduct applies within all community spaces, and also
|
|
||||||
applies when an individual is officially representing the community in
|
|
||||||
public spaces. Examples of representing our community include using an
|
|
||||||
official e-mail address, posting via an official social media account,
|
|
||||||
or acting as an appointed representative at an online or offline
|
|
||||||
event.
|
|
||||||
|
|
||||||
## Enforcement
|
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior
|
|
||||||
may be reported to the community leaders responsible for enforcement
|
|
||||||
at our Discord channel. All complaints
|
|
||||||
will be reviewed and investigated promptly and fairly.
|
|
||||||
|
|
||||||
All community leaders are obligated to respect the privacy and
|
|
||||||
security of the reporter of any incident.
|
|
||||||
|
|
||||||
## Enforcement Guidelines
|
|
||||||
|
|
||||||
Community leaders will follow these Community Impact Guidelines in
|
|
||||||
determining the consequences for any action they deem in violation of
|
|
||||||
this Code of Conduct:
|
|
||||||
|
|
||||||
### 1. Correction
|
|
||||||
|
|
||||||
**Community Impact**: Use of inappropriate language or other behavior
|
|
||||||
deemed unprofessional or unwelcome in the community.
|
|
||||||
|
|
||||||
**Consequence**: A private, written warning from community leaders,
|
|
||||||
providing clarity around the nature of the violation and an
|
|
||||||
explanation of why the behavior was inappropriate. A public apology
|
|
||||||
may be requested.
|
|
||||||
|
|
||||||
### 2. Warning
|
|
||||||
|
|
||||||
**Community Impact**: A violation through a single incident or series
|
|
||||||
of actions.
|
|
||||||
|
|
||||||
**Consequence**: A warning with consequences for continued
|
|
||||||
behavior. No interaction with the people involved, including
|
|
||||||
unsolicited interaction with those enforcing the Code of Conduct, for
|
|
||||||
a specified period of time. This includes avoiding interactions in
|
|
||||||
community spaces as well as external channels like social
|
|
||||||
media. Violating these terms may lead to a temporary or permanent ban.
|
|
||||||
|
|
||||||
### 3. Temporary Ban
|
|
||||||
|
|
||||||
**Community Impact**: A serious violation of community standards,
|
|
||||||
including sustained inappropriate behavior.
|
|
||||||
|
|
||||||
**Consequence**: A temporary ban from any sort of interaction or
|
|
||||||
public communication with the community for a specified period of
|
|
||||||
time. No public or private interaction with the people involved,
|
|
||||||
including unsolicited interaction with those enforcing the Code of
|
|
||||||
Conduct, is allowed during this period. Violating these terms may lead
|
|
||||||
to a permanent ban.
|
|
||||||
|
|
||||||
### 4. Permanent Ban
|
|
||||||
|
|
||||||
**Community Impact**: Demonstrating a pattern of violation of
|
|
||||||
community standards, including sustained inappropriate behavior,
|
|
||||||
harassment of an individual, or aggression toward or disparagement of
|
|
||||||
classes of individuals.
|
|
||||||
|
|
||||||
**Consequence**: A permanent ban from any sort of public interaction
|
|
||||||
within the community.
|
|
||||||
|
|
||||||
## Attribution
|
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor
|
|
||||||
Covenant][homepage], version 2.0, available at
|
|
||||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
|
||||||
|
|
||||||
Community Impact Guidelines were inspired by [Mozilla's code of
|
|
||||||
conduct enforcement ladder](https://github.com/mozilla/diversity).
|
|
||||||
|
|
||||||
[homepage]: https://www.contributor-covenant.org
|
|
||||||
|
|
||||||
For answers to common questions about this code of conduct, see the
|
|
||||||
FAQ at https://www.contributor-covenant.org/faq. Translations are
|
|
||||||
available at https://www.contributor-covenant.org/translations.
|
|
||||||
@@ -1,152 +1,58 @@
|
|||||||
dev:
|
dev:
|
||||||
@trap 'make dev-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.dev.yml up --remove-orphans
|
@printf "This command has been removed. Please use:\n\n mise dev # or mise //:dev from another directory\n\n" >&2 && exit 1
|
||||||
|
|
||||||
dev-down:
|
dev-down:
|
||||||
docker compose -f ./docker/docker-compose.dev.yml down --remove-orphans
|
@printf "This command has been removed. Please use:\n\n mise dev-down # or mise //:dev-down from another directory\n\n" >&2 && exit 1
|
||||||
|
|
||||||
dev-update:
|
dev-update:
|
||||||
@trap 'make dev-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans
|
@printf "This command has been removed. Please use:\n\n mise dev-update # or mise //:dev-update from another directory\n\n" >&2 && exit 1
|
||||||
|
|
||||||
dev-scale:
|
dev-scale:
|
||||||
@trap 'make dev-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.dev.yml up --build -V --scale immich-server=3 --remove-orphans
|
@printf "This command has been removed. Please use:\n\n mise dev-scale # or mise //:dev-scale from another directory\n\n" >&2 && exit 1
|
||||||
|
|
||||||
dev-docs:
|
dev-docs:
|
||||||
npm --prefix docs run start
|
npm --prefix docs run start
|
||||||
|
|
||||||
.PHONY: e2e
|
.PHONY: e2e
|
||||||
e2e:
|
e2e:
|
||||||
@trap 'make e2e-down' EXIT; COMPOSE_BAKE=true docker compose -f ./e2e/docker-compose.yml up --remove-orphans
|
@printf "This command has been removed. Please use:\n\n mise e2e # or mise //:e2e from another directory\n\n" >&2 && exit 1
|
||||||
|
|
||||||
e2e-dev:
|
e2e-dev:
|
||||||
@trap 'make e2e-down' EXIT; COMPOSE_BAKE=true docker compose -f ./e2e/docker-compose.dev.yml up --remove-orphans
|
@printf "This command has been removed. Please use:\n\n mise e2e-dev # or mise //:e2e-dev from another directory\n\n" >&2 && exit 1
|
||||||
|
|
||||||
e2e-update:
|
e2e-update:
|
||||||
@trap 'make e2e-down' EXIT; COMPOSE_BAKE=true docker compose -f ./e2e/docker-compose.yml up --build -V --remove-orphans
|
@printf "This command has been removed. Please use:\n\n mise e2e-update # or mise //:e2e-update from another directory\n\n" >&2 && exit 1
|
||||||
|
|
||||||
e2e-down:
|
e2e-down:
|
||||||
docker compose -f ./e2e/docker-compose.yml down --remove-orphans
|
@printf "This command has been removed. Please use:\n\n mise e2e-down # or mise //:e2e-down from another directory\n\n" >&2 && exit 1
|
||||||
|
|
||||||
prod:
|
prod:
|
||||||
@trap 'make prod-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.prod.yml up --build -V --remove-orphans
|
@printf "This command has been removed. Please use:\n\n mise prod # or mise //:prod from another directory\n\n" >&2 && exit 1
|
||||||
|
|
||||||
prod-down:
|
prod-down:
|
||||||
docker compose -f ./docker/docker-compose.prod.yml down --remove-orphans
|
@printf "This command has been removed. Please use:\n\n mise prod-down # or mise //:prod-down from another directory\n\n" >&2 && exit 1
|
||||||
|
|
||||||
prod-scale:
|
prod-scale:
|
||||||
@trap 'make prod-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.prod.yml up --build -V --scale immich-server=3 --scale immich-microservices=3 --remove-orphans
|
@printf "This command has been removed. Please use:\n\n mise prod-scale # or mise //:prod-scale from another directory\n\n" >&2 && exit 1
|
||||||
|
|
||||||
.PHONY: open-api
|
.PHONY: open-api
|
||||||
open-api:
|
open-api:
|
||||||
cd ./open-api && bash ./bin/generate-open-api.sh
|
@printf "This command has been removed. Please use:\n\n mise open-api # or mise //:open-api from another directory\n\n" >&2 && exit 1
|
||||||
|
|
||||||
open-api-dart:
|
|
||||||
cd ./open-api && bash ./bin/generate-open-api.sh dart
|
|
||||||
|
|
||||||
open-api-typescript:
|
|
||||||
cd ./open-api && bash ./bin/generate-open-api.sh typescript
|
|
||||||
|
|
||||||
sql:
|
sql:
|
||||||
pnpm --filter immich run sync:sql
|
@printf "This command has been removed. Please use:\n\n mise sql # or mise //:sql from another directory\n\n" >&2 && exit 1
|
||||||
|
|
||||||
attach-server:
|
|
||||||
docker exec -it docker_immich-server_1 sh
|
|
||||||
|
|
||||||
renovate:
|
renovate:
|
||||||
LOG_LEVEL=debug pnpm exec renovate --platform=local --repository-cache=reset
|
LOG_LEVEL=debug pnpm exec renovate --platform=local --repository-cache=reset
|
||||||
|
|
||||||
# Directories that need to be created for volumes or build output
|
|
||||||
VOLUME_DIRS = \
|
|
||||||
./.pnpm-store \
|
|
||||||
./web/.svelte-kit \
|
|
||||||
./web/node_modules \
|
|
||||||
./web/coverage \
|
|
||||||
./e2e/node_modules \
|
|
||||||
./docs/node_modules \
|
|
||||||
./server/node_modules \
|
|
||||||
./open-api/typescript-sdk/node_modules \
|
|
||||||
./.github/node_modules \
|
|
||||||
./node_modules \
|
|
||||||
./cli/node_modules
|
|
||||||
|
|
||||||
# Include .env file if it exists
|
# Include .env file if it exists
|
||||||
-include docker/.env
|
-include docker/.env
|
||||||
|
|
||||||
MODULES = e2e server web cli sdk docs .github
|
MODULES = e2e server web cli sdk docs .github
|
||||||
|
|
||||||
# directory to package name mapping function
|
|
||||||
# cli = @immich/cli
|
|
||||||
# docs = documentation
|
|
||||||
# e2e = immich-e2e
|
|
||||||
# open-api/typescript-sdk = @immich/sdk
|
|
||||||
# server = immich
|
|
||||||
# web = immich-web
|
|
||||||
map-package = $(subst sdk,@immich/sdk,$(subst cli,@immich/cli,$(subst docs,documentation,$(subst e2e,immich-e2e,$(subst server,immich,$(subst web,immich-web,$1))))))
|
|
||||||
|
|
||||||
audit-%:
|
|
||||||
pnpm --filter $(call map-package,$*) audit fix
|
|
||||||
install-%:
|
|
||||||
pnpm --filter $(call map-package,$*) install $(if $(FROZEN),--frozen-lockfile) $(if $(OFFLINE),--offline)
|
|
||||||
build-cli: build-sdk
|
|
||||||
build-web: build-sdk
|
|
||||||
build-%: install-%
|
|
||||||
pnpm --filter $(call map-package,$*) run build
|
|
||||||
format-%:
|
|
||||||
pnpm --filter $(call map-package,$*) run format:fix
|
|
||||||
lint-%:
|
|
||||||
pnpm --filter $(call map-package,$*) run lint:fix
|
|
||||||
check-%:
|
|
||||||
pnpm --filter $(call map-package,$*) run check
|
|
||||||
check-web:
|
|
||||||
pnpm --filter immich-web run check:typescript
|
|
||||||
pnpm --filter immich-web run check:svelte
|
|
||||||
test-%:
|
|
||||||
pnpm --filter $(call map-package,$*) run test
|
|
||||||
test-e2e:
|
test-e2e:
|
||||||
docker compose -f ./e2e/docker-compose.yml build
|
@printf "This command has been removed. Please use:\n\n mise //e2e:test # or mise //e2e:test-web for web tests, respectively\n\n" >&2 && exit 1
|
||||||
pnpm --filter immich-e2e run test
|
|
||||||
pnpm --filter immich-e2e run test:web
|
|
||||||
test-medium:
|
|
||||||
docker run \
|
|
||||||
--rm \
|
|
||||||
-v ./server/src:/usr/src/app/src \
|
|
||||||
-v ./server/test:/usr/src/app/test \
|
|
||||||
-v ./server/vitest.config.medium.mjs:/usr/src/app/vitest.config.medium.mjs \
|
|
||||||
-v ./server/tsconfig.json:/usr/src/app/tsconfig.json \
|
|
||||||
-e NODE_ENV=development \
|
|
||||||
immich-server:latest \
|
|
||||||
-c "pnpm test:medium -- --run"
|
|
||||||
test-medium-dev:
|
|
||||||
docker exec -it immich_server /bin/sh -c "pnpm run test:medium"
|
|
||||||
|
|
||||||
install-all:
|
|
||||||
pnpm -r --filter '!documentation' install
|
|
||||||
|
|
||||||
build-all: $(foreach M,$(filter-out e2e docs .github,$(MODULES)),build-$M) ;
|
|
||||||
|
|
||||||
check-all:
|
|
||||||
pnpm -r --filter '!documentation' run "/^(check|check\:svelte|check\:typescript)$/"
|
|
||||||
lint-all:
|
|
||||||
pnpm -r --filter '!documentation' run lint:fix
|
|
||||||
format-all:
|
|
||||||
pnpm -r --filter '!documentation' run format:fix
|
|
||||||
audit-all:
|
|
||||||
pnpm -r --filter '!documentation' audit fix
|
|
||||||
hygiene-all: audit-all
|
|
||||||
pnpm -r --filter '!documentation' run "/(format:fix|check|check:svelte|check:typescript|sql)/"
|
|
||||||
|
|
||||||
test-all:
|
|
||||||
pnpm -r --filter '!documentation' run "/^test/"
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
find . -name "node_modules" -type d -prune -exec rm -rf {} +
|
@printf "This command has been removed. Please use:\n\n mise clean # or mise //:clean from another directory\n\n" >&2 && exit 1
|
||||||
find . -name "dist" -type d -prune -exec rm -rf '{}' +
|
|
||||||
find . -name "build" -type d -prune -exec rm -rf '{}' +
|
|
||||||
find . -name ".svelte-kit" -type d -prune -exec rm -rf '{}' +
|
|
||||||
find . -name "coverage" -type d -prune -exec rm -rf '{}' +
|
|
||||||
find . -name ".pnpm-store" -type d -prune -exec rm -rf '{}' +
|
|
||||||
command -v docker >/dev/null 2>&1 && docker compose -f ./docker/docker-compose.dev.yml down -v --remove-orphans || true
|
|
||||||
command -v docker >/dev/null 2>&1 && docker compose -f ./e2e/docker-compose.yml down -v --remove-orphans || true
|
|
||||||
|
|
||||||
|
|
||||||
setup-server-dev: install-server
|
|
||||||
setup-web-dev: install-sdk build-sdk install-web
|
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
# Security Policy
|
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
|
||||||
|
|
||||||
Please report security issues to `security@immich.app`
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
24.15.0
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
FROM node:24.1.0-alpine3.20@sha256:8fe019e0d57dbdce5f5c27c0b63d2775cf34b00e3755a7dea969802d7e0c2b25 AS core
|
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
|
||||||
COPY package* pnpm* .pnpmfile.cjs ./
|
|
||||||
COPY ./cli ./cli/
|
|
||||||
COPY ./open-api/typescript-sdk ./open-api/typescript-sdk/
|
|
||||||
RUN corepack enable pnpm && \
|
|
||||||
pnpm install --filter @immich/sdk --filter @immich/cli --frozen-lockfile && \
|
|
||||||
pnpm --filter @immich/sdk build && \
|
|
||||||
pnpm --filter @immich/cli build
|
|
||||||
|
|
||||||
WORKDIR /import
|
|
||||||
|
|
||||||
ENTRYPOINT ["node", "/usr/src/app/cli/dist"]
|
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html
|
||||||
|
|
||||||
|
[[tools.opentofu]]
|
||||||
|
version = "1.11.6"
|
||||||
|
backend = "aqua:opentofu/opentofu"
|
||||||
|
|
||||||
|
[tools.opentofu."platforms.linux-arm64"]
|
||||||
|
checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850"
|
||||||
|
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz"
|
||||||
|
|
||||||
|
[tools.opentofu."platforms.linux-arm64-musl"]
|
||||||
|
checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850"
|
||||||
|
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz"
|
||||||
|
|
||||||
|
[tools.opentofu."platforms.linux-x64"]
|
||||||
|
checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e"
|
||||||
|
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz"
|
||||||
|
|
||||||
|
[tools.opentofu."platforms.linux-x64-musl"]
|
||||||
|
checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e"
|
||||||
|
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz"
|
||||||
|
|
||||||
|
[tools.opentofu."platforms.macos-arm64"]
|
||||||
|
checksum = "sha256:62d7fa8539e13b444827aa0a3b90c5972da5c47e8f8882d9dcf2e430e78840c1"
|
||||||
|
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_arm64.tar.gz"
|
||||||
|
|
||||||
|
[tools.opentofu."platforms.macos-x64"]
|
||||||
|
checksum = "sha256:1408cdef1c380f914565e6b4bb70794c6b163f195fcb233357f3d6c5745906b6"
|
||||||
|
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_amd64.tar.gz"
|
||||||
|
|
||||||
|
[tools.opentofu."platforms.windows-x64"]
|
||||||
|
checksum = "sha256:27323f70c875b8251bfd7e61a4cffc3ebff4e56ed1e611b955016f0c7077367e"
|
||||||
|
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_windows_amd64.tar.gz"
|
||||||
|
|
||||||
|
[[tools.terragrunt]]
|
||||||
|
version = "1.0.3"
|
||||||
|
backend = "aqua:gruntwork-io/terragrunt"
|
||||||
|
|
||||||
|
[tools.terragrunt."platforms.linux-arm64"]
|
||||||
|
checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe"
|
||||||
|
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz"
|
||||||
|
|
||||||
|
[tools.terragrunt."platforms.linux-arm64-musl"]
|
||||||
|
checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe"
|
||||||
|
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz"
|
||||||
|
|
||||||
|
[tools.terragrunt."platforms.linux-x64"]
|
||||||
|
checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073"
|
||||||
|
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz"
|
||||||
|
|
||||||
|
[tools.terragrunt."platforms.linux-x64-musl"]
|
||||||
|
checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073"
|
||||||
|
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz"
|
||||||
|
|
||||||
|
[tools.terragrunt."platforms.macos-arm64"]
|
||||||
|
checksum = "sha256:aacb5be2ca5475300cbce246dfbd8a45eb47510fbaa70fab8561c49ef5db03aa"
|
||||||
|
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_arm64.tar.gz"
|
||||||
|
|
||||||
|
[tools.terragrunt."platforms.macos-x64"]
|
||||||
|
checksum = "sha256:3133c2251e191aede8e3dd2a5b3aee2e91c5f08f88f117aee40eed9a24c8ef6b"
|
||||||
|
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_amd64.tar.gz"
|
||||||
|
|
||||||
|
[tools.terragrunt."platforms.windows-x64"]
|
||||||
|
checksum = "sha256:183b2745b4e04980a6bfa4450ff81956a12596ca22d70f7aaa793980f5b036db"
|
||||||
|
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_windows_amd64.exe.tar.gz"
|
||||||
+30
-30
@@ -2,37 +2,37 @@
|
|||||||
# Manual edits may be lost in future updates.
|
# Manual edits may be lost in future updates.
|
||||||
|
|
||||||
provider "registry.opentofu.org/cloudflare/cloudflare" {
|
provider "registry.opentofu.org/cloudflare/cloudflare" {
|
||||||
version = "4.52.5"
|
version = "4.52.7"
|
||||||
constraints = "4.52.5"
|
constraints = "4.52.7"
|
||||||
hashes = [
|
hashes = [
|
||||||
"h1:+rfzF+16ZcWZWnTyW/p1HHTzYbPKX8Zt2nIFtR/+f+E=",
|
"h1:+O72J3QYiZtYmYYZM/Eh0f4NNfl1BvjX1eju43qTQsQ=",
|
||||||
"h1:18bXaaOSq8MWKuMxo/4y7EB7/i7G90y5QsKHZRmkoDo=",
|
"h1:0oqjYIPXcXh7XiDiKI085cHDYQQ5mh8kDl9dmBtvtog=",
|
||||||
"h1:4vZVOpKeEQZsF2VrARRZFeL37Ed/gD4rRMtfnvWQres=",
|
"h1:4b4ESb87MGv5bnadgYe7sK5rEkKMZhbkQcwPubQTsR4=",
|
||||||
"h1:BZOsTF83QPKXTAaYqxPKzdl1KRjk/L2qbPpFjM0w28A=",
|
"h1:6mTr3eA1Ddb348lLmJuyvn98z4KF+ejqaUEJ76D1rzQ=",
|
||||||
"h1:CDuC+HXLvc1z6wkCRsSDcc/+QENIHEtssYshiWg3opA=",
|
"h1:9/3YH+9k9HqsvFtbmBf7SO2+xqZeZrXNKzLkjNuhUEA=",
|
||||||
"h1:DE+YFzLnqSe79pI2R4idRGx5QzLdrA7RXvngTkGfZ30=",
|
"h1:Jcq4tBWgyH4/2JsojNBSRaN0mcItVMchO+lynonrlqc=",
|
||||||
"h1:DfaJwH3Ml4yrRbdAY4AcDVy0QTQk5T3A622TXzS/u2E=",
|
"h1:Y4Vv/2RdP0Q+uxqhOxzOdKxuuEMjXPDcU0vPc5bCQzI=",
|
||||||
"h1:EIDXP0W3kgIv2pecrFmqtK/DnlqkyckzBzhxKaXU+4A=",
|
"h1:a0gW8FBKsbP9Fi0HEDoy49WIbEWVHk9+BR4/iwuBdDQ=",
|
||||||
"h1:EV4kYyaOnwGA0bh/3hU6Ezqnt1PFDxopH7i85e48IzY=",
|
"h1:gElv6iqJtg8OKN77gbw+MjrkrQmJHPkkMEi1J+0xkpU=",
|
||||||
"h1:M0iXabfzamU+MPDi0G9XACpbacFKMakmM+Z9HZ8HrsM=",
|
"h1:oslXUugD/NQ+duJgT4BhKQyfGbuFOANknMvR73fiOeM=",
|
||||||
"h1:YWmCbGF/KbsrUzcYVBLscwLizidbp95TDQa0N2qpmVo=",
|
"h1:pPItIWii5oymR+geZB219ROSPuSODPLTlM4S/u8xLvM=",
|
||||||
"h1:cxPcCB5gbrpUO1+IXkQYs1YTY50/0IlApCzGea0cwuQ=",
|
"h1:u67GWw8GwD9NDlDzp9Y5VRnSQGcCrE8rSpkGPaBpDl0=",
|
||||||
"h1:g6DldikTV2HXUu9uoeNY5FuLufgaYWF4ufgZg7wq62s=",
|
"h1:uUUa9dY0XQOycI8pxg16PFFtL0WCTi9uEJz8trTQ7pU=",
|
||||||
"h1:oi/Hrx9pwoQ+Z52CBC+rrowVH387EIj0qvnxQgDeI+0=",
|
"h1:y3rV8KF2q6GEMANNlf5EkKJurlfbKlIKpjGcdxoy7pQ=",
|
||||||
"zh:1a3400cb38863b2585968d1876706bcfc67a148e1318a1d325c6c7704adc999b",
|
"zh:0c904ce31a4c6c4a5b3bf7ff1560e77c0cc7e2450c8553ded8e8c90398e1418b",
|
||||||
"zh:4c5062cb9e9da1676f06ae92b8370186d98976cc4c7030d3cd76df12af54282a",
|
"zh:36183d310c36373fe4cb936b83c595c6fd3b0a94bc7827f28e5789ccbf59752e",
|
||||||
"zh:52110f493b5f0587ef77a1cfd1a67001fd4c617b14c6502d732ab47352bdc2f7",
|
"zh:556a568a6f0235e8f41647de9e4d3a1e7b1d6502df8b19b54ec441f1c653ea10",
|
||||||
"zh:5aa536f9eaeb43823aaf2aa80e7d39b25ef2b383405ed034aa16a28b446a9238",
|
"zh:633ebbd5b0245e75e500ef9be4d9e62288f97e8da3baaa51323892a786d90285",
|
||||||
"zh:5cc39459a1c6be8a918f17054e4fbba573825ed5597dcada588fe99614d98a5b",
|
"zh:6acfe60cf52a65ba8f044f748548d2119e7f4fd7f8ebcb14698960d87c68f529",
|
||||||
"zh:629ae6a7ba298815131da826474d199312d21cec53a4d5ded4fa56a692e6f072",
|
|
||||||
"zh:719cc7c75dc1d3eb30c22ff5102a017996d9788b948078c7e1c5b3446aeca661",
|
|
||||||
"zh:8698635a3ca04383c1e93b21d6963346bdae54d27177a48e4b1435b7f731731c",
|
|
||||||
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
|
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
|
||||||
"zh:8a9993f1dcadf1dd6ca43b23348abe374605d29945a2fafc07fb3457644e6a54",
|
"zh:904acc31ebb9d6ef68c792074b30532ee61bf515f19e0a3c75b46f126cca1f13",
|
||||||
"zh:b1b9a1e6bcc24d5863a664a411d2dc906373ae7a2399d2d65548ce7377057852",
|
"zh:a1d0a81246afc8750286d3f6fe7a8fbe6460dd2662407b28dbfbabb612e5fa9d",
|
||||||
"zh:b270184cdeec277218e84b94cb136fead753da717f9b9dc378e51907f3f00bb0",
|
"zh:a41a36fe253fc365fe2b7ffc749624688b2693b4634862fda161179ab100029f",
|
||||||
"zh:dff2bc10071210181726ce270f954995fe42c696e61e2e8f874021fed02521e5",
|
"zh:a7ef269e77ffa8715c8945a2c14322c7ff159ea44c15f62505f3cbb2cae3b32d",
|
||||||
"zh:e8e87b40b6a87dc097b0fdc20d3f725cec0d82abc9cc3755c1f89f8f6e8b0036",
|
"zh:b01aa3bed30610633b762df64332b26f8844a68c3960cebcb30f04918efc67fe",
|
||||||
"zh:ee964a6573d399a5dd22ce328fb38ca1207797a02248f14b2e4913ee390e7803",
|
"zh:b069cc2cd18cae10757df3ae030508eac8d55de7e49eda7a5e3e11f2f7fe6455",
|
||||||
|
"zh:b2d2c6313729ebb7465dceece374049e2d08bda34473901be9ff46a8836d42b2",
|
||||||
|
"zh:db0e114edaf4bc2f3d4769958807c83022bfbc619a00bdf4c4bd17faa4ab2d8b",
|
||||||
|
"zh:ecc0aa8b9044f664fd2aaf8fa992d976578f78478980555b4b8f6148e8d1a5fe",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
cloudflare = {
|
cloudflare = {
|
||||||
source = "cloudflare/cloudflare"
|
source = "cloudflare/cloudflare"
|
||||||
version = "4.52.5"
|
version = "4.52.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-30
@@ -2,37 +2,37 @@
|
|||||||
# Manual edits may be lost in future updates.
|
# Manual edits may be lost in future updates.
|
||||||
|
|
||||||
provider "registry.opentofu.org/cloudflare/cloudflare" {
|
provider "registry.opentofu.org/cloudflare/cloudflare" {
|
||||||
version = "4.52.5"
|
version = "4.52.7"
|
||||||
constraints = "4.52.5"
|
constraints = "4.52.7"
|
||||||
hashes = [
|
hashes = [
|
||||||
"h1:+rfzF+16ZcWZWnTyW/p1HHTzYbPKX8Zt2nIFtR/+f+E=",
|
"h1:+O72J3QYiZtYmYYZM/Eh0f4NNfl1BvjX1eju43qTQsQ=",
|
||||||
"h1:18bXaaOSq8MWKuMxo/4y7EB7/i7G90y5QsKHZRmkoDo=",
|
"h1:0oqjYIPXcXh7XiDiKI085cHDYQQ5mh8kDl9dmBtvtog=",
|
||||||
"h1:4vZVOpKeEQZsF2VrARRZFeL37Ed/gD4rRMtfnvWQres=",
|
"h1:4b4ESb87MGv5bnadgYe7sK5rEkKMZhbkQcwPubQTsR4=",
|
||||||
"h1:BZOsTF83QPKXTAaYqxPKzdl1KRjk/L2qbPpFjM0w28A=",
|
"h1:6mTr3eA1Ddb348lLmJuyvn98z4KF+ejqaUEJ76D1rzQ=",
|
||||||
"h1:CDuC+HXLvc1z6wkCRsSDcc/+QENIHEtssYshiWg3opA=",
|
"h1:9/3YH+9k9HqsvFtbmBf7SO2+xqZeZrXNKzLkjNuhUEA=",
|
||||||
"h1:DE+YFzLnqSe79pI2R4idRGx5QzLdrA7RXvngTkGfZ30=",
|
"h1:Jcq4tBWgyH4/2JsojNBSRaN0mcItVMchO+lynonrlqc=",
|
||||||
"h1:DfaJwH3Ml4yrRbdAY4AcDVy0QTQk5T3A622TXzS/u2E=",
|
"h1:Y4Vv/2RdP0Q+uxqhOxzOdKxuuEMjXPDcU0vPc5bCQzI=",
|
||||||
"h1:EIDXP0W3kgIv2pecrFmqtK/DnlqkyckzBzhxKaXU+4A=",
|
"h1:a0gW8FBKsbP9Fi0HEDoy49WIbEWVHk9+BR4/iwuBdDQ=",
|
||||||
"h1:EV4kYyaOnwGA0bh/3hU6Ezqnt1PFDxopH7i85e48IzY=",
|
"h1:gElv6iqJtg8OKN77gbw+MjrkrQmJHPkkMEi1J+0xkpU=",
|
||||||
"h1:M0iXabfzamU+MPDi0G9XACpbacFKMakmM+Z9HZ8HrsM=",
|
"h1:oslXUugD/NQ+duJgT4BhKQyfGbuFOANknMvR73fiOeM=",
|
||||||
"h1:YWmCbGF/KbsrUzcYVBLscwLizidbp95TDQa0N2qpmVo=",
|
"h1:pPItIWii5oymR+geZB219ROSPuSODPLTlM4S/u8xLvM=",
|
||||||
"h1:cxPcCB5gbrpUO1+IXkQYs1YTY50/0IlApCzGea0cwuQ=",
|
"h1:u67GWw8GwD9NDlDzp9Y5VRnSQGcCrE8rSpkGPaBpDl0=",
|
||||||
"h1:g6DldikTV2HXUu9uoeNY5FuLufgaYWF4ufgZg7wq62s=",
|
"h1:uUUa9dY0XQOycI8pxg16PFFtL0WCTi9uEJz8trTQ7pU=",
|
||||||
"h1:oi/Hrx9pwoQ+Z52CBC+rrowVH387EIj0qvnxQgDeI+0=",
|
"h1:y3rV8KF2q6GEMANNlf5EkKJurlfbKlIKpjGcdxoy7pQ=",
|
||||||
"zh:1a3400cb38863b2585968d1876706bcfc67a148e1318a1d325c6c7704adc999b",
|
"zh:0c904ce31a4c6c4a5b3bf7ff1560e77c0cc7e2450c8553ded8e8c90398e1418b",
|
||||||
"zh:4c5062cb9e9da1676f06ae92b8370186d98976cc4c7030d3cd76df12af54282a",
|
"zh:36183d310c36373fe4cb936b83c595c6fd3b0a94bc7827f28e5789ccbf59752e",
|
||||||
"zh:52110f493b5f0587ef77a1cfd1a67001fd4c617b14c6502d732ab47352bdc2f7",
|
"zh:556a568a6f0235e8f41647de9e4d3a1e7b1d6502df8b19b54ec441f1c653ea10",
|
||||||
"zh:5aa536f9eaeb43823aaf2aa80e7d39b25ef2b383405ed034aa16a28b446a9238",
|
"zh:633ebbd5b0245e75e500ef9be4d9e62288f97e8da3baaa51323892a786d90285",
|
||||||
"zh:5cc39459a1c6be8a918f17054e4fbba573825ed5597dcada588fe99614d98a5b",
|
"zh:6acfe60cf52a65ba8f044f748548d2119e7f4fd7f8ebcb14698960d87c68f529",
|
||||||
"zh:629ae6a7ba298815131da826474d199312d21cec53a4d5ded4fa56a692e6f072",
|
|
||||||
"zh:719cc7c75dc1d3eb30c22ff5102a017996d9788b948078c7e1c5b3446aeca661",
|
|
||||||
"zh:8698635a3ca04383c1e93b21d6963346bdae54d27177a48e4b1435b7f731731c",
|
|
||||||
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
|
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
|
||||||
"zh:8a9993f1dcadf1dd6ca43b23348abe374605d29945a2fafc07fb3457644e6a54",
|
"zh:904acc31ebb9d6ef68c792074b30532ee61bf515f19e0a3c75b46f126cca1f13",
|
||||||
"zh:b1b9a1e6bcc24d5863a664a411d2dc906373ae7a2399d2d65548ce7377057852",
|
"zh:a1d0a81246afc8750286d3f6fe7a8fbe6460dd2662407b28dbfbabb612e5fa9d",
|
||||||
"zh:b270184cdeec277218e84b94cb136fead753da717f9b9dc378e51907f3f00bb0",
|
"zh:a41a36fe253fc365fe2b7ffc749624688b2693b4634862fda161179ab100029f",
|
||||||
"zh:dff2bc10071210181726ce270f954995fe42c696e61e2e8f874021fed02521e5",
|
"zh:a7ef269e77ffa8715c8945a2c14322c7ff159ea44c15f62505f3cbb2cae3b32d",
|
||||||
"zh:e8e87b40b6a87dc097b0fdc20d3f725cec0d82abc9cc3755c1f89f8f6e8b0036",
|
"zh:b01aa3bed30610633b762df64332b26f8844a68c3960cebcb30f04918efc67fe",
|
||||||
"zh:ee964a6573d399a5dd22ce328fb38ca1207797a02248f14b2e4913ee390e7803",
|
"zh:b069cc2cd18cae10757df3ae030508eac8d55de7e49eda7a5e3e11f2f7fe6455",
|
||||||
|
"zh:b2d2c6313729ebb7465dceece374049e2d08bda34473901be9ff46a8836d42b2",
|
||||||
|
"zh:db0e114edaf4bc2f3d4769958807c83022bfbc619a00bdf4c4bd17faa4ab2d8b",
|
||||||
|
"zh:ecc0aa8b9044f664fd2aaf8fa992d976578f78478980555b4b8f6148e8d1a5fe",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
cloudflare = {
|
cloudflare = {
|
||||||
source = "cloudflare/cloudflare"
|
source = "cloudflare/cloudflare"
|
||||||
version = "4.52.5"
|
version = "4.52.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,14 +21,14 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ..:/usr/src/app
|
- ..:/usr/src/app
|
||||||
# - ../../ui:/usr/src/ui
|
# - ../../ui:/usr/src/ui
|
||||||
- pnpm_cache:/buildcache/pnpm_cache
|
- build_cache:/buildcache
|
||||||
- server_node_modules:/usr/src/app/server/node_modules
|
- server_node_modules:/usr/src/app/server/node_modules
|
||||||
- web_node_modules:/usr/src/app/web/node_modules
|
- web_node_modules:/usr/src/app/web/node_modules
|
||||||
- github_node_modules:/usr/src/app/.github/node_modules
|
- github_node_modules:/usr/src/app/.github/node_modules
|
||||||
- cli_node_modules:/usr/src/app/cli/node_modules
|
- cli_node_modules:/usr/src/app/packages/cli/node_modules
|
||||||
- docs_node_modules:/usr/src/app/docs/node_modules
|
- docs_node_modules:/usr/src/app/docs/node_modules
|
||||||
- e2e_node_modules:/usr/src/app/e2e/node_modules
|
- e2e_node_modules:/usr/src/app/e2e/node_modules
|
||||||
- sdk_node_modules:/usr/src/app/open-api/typescript-sdk/node_modules
|
- sdk_node_modules:/usr/src/app/packages/sdk/node_modules
|
||||||
- app_node_modules:/usr/src/app/node_modules
|
- app_node_modules:/usr/src/app/node_modules
|
||||||
- sveltekit:/usr/src/app/web/.svelte-kit
|
- sveltekit:/usr/src/app/web/.svelte-kit
|
||||||
- coverage:/usr/src/app/web/coverage
|
- coverage:/usr/src/app/web/coverage
|
||||||
@@ -45,11 +45,11 @@ services:
|
|||||||
target: dev
|
target: dev
|
||||||
command:
|
command:
|
||||||
- |
|
- |
|
||||||
pnpm install
|
mise install
|
||||||
touch /tmp/init-complete
|
touch /tmp/init-complete
|
||||||
exec tail -f /dev/null
|
exec tail -f /dev/null
|
||||||
volumes:
|
volumes:
|
||||||
- pnpm_store_server:/buildcache/pnpm-store
|
- build_cache:/buildcache
|
||||||
restart: 'no'
|
restart: 'no'
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ['CMD', 'test', '-f', '/tmp/init-complete']
|
test: ['CMD', 'test', '-f', '/tmp/init-complete']
|
||||||
@@ -73,8 +73,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ${UPLOAD_LOCATION}/photos:/data
|
- ${UPLOAD_LOCATION}/photos:/data
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
- pnpm_store_server:/buildcache/pnpm-store
|
- ../packages/plugin-core:/build/plugins/immich-plugin-core
|
||||||
- ../plugins:/build/corePlugin
|
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
@@ -122,8 +121,6 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
- 24678:24678
|
- 24678:24678
|
||||||
volumes:
|
|
||||||
- pnpm_store_web:/buildcache/pnpm-store
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
depends_on:
|
||||||
immich-init:
|
immich-init:
|
||||||
@@ -157,7 +154,7 @@ services:
|
|||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: immich_redis
|
container_name: immich_redis
|
||||||
image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9
|
image: docker.io/valkey/valkey:9@sha256:4963247afc4cd33c7d3b2d2816b9f7f8eeebab148d29056c2ca4d7cbc966f2d9
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: redis-cli ping || exit 1
|
test: redis-cli ping || exit 1
|
||||||
|
|
||||||
@@ -203,9 +200,7 @@ volumes:
|
|||||||
model_cache:
|
model_cache:
|
||||||
prometheus_data:
|
prometheus_data:
|
||||||
grafana_data:
|
grafana_data:
|
||||||
pnpm_cache:
|
build_cache:
|
||||||
pnpm_store_server:
|
|
||||||
pnpm_store_web:
|
|
||||||
server_node_modules:
|
server_node_modules:
|
||||||
web_node_modules:
|
web_node_modules:
|
||||||
github_node_modules:
|
github_node_modules:
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ services:
|
|||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: immich_redis
|
container_name: immich_redis
|
||||||
image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9
|
image: docker.io/valkey/valkey:9@sha256:4963247afc4cd33c7d3b2d2816b9f7f8eeebab148d29056c2ca4d7cbc966f2d9
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: redis-cli ping || exit 1
|
test: redis-cli ping || exit 1
|
||||||
restart: always
|
restart: always
|
||||||
@@ -85,7 +85,7 @@ services:
|
|||||||
container_name: immich_prometheus
|
container_name: immich_prometheus
|
||||||
ports:
|
ports:
|
||||||
- 9090:9090
|
- 9090:9090
|
||||||
image: prom/prometheus@sha256:e4254400b85610324913f0dc4acf92603d9984e7519414c5a12811aa6146acc3
|
image: prom/prometheus@sha256:69f5241418838263316593f7274a304b095c40bcf22e57272865da91bd60a8ac
|
||||||
volumes:
|
volumes:
|
||||||
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
||||||
- prometheus-data:/prometheus
|
- prometheus-data:/prometheus
|
||||||
@@ -97,7 +97,7 @@ services:
|
|||||||
command: ['./run.sh', '-disable-reporting']
|
command: ['./run.sh', '-disable-reporting']
|
||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
image: grafana/grafana:12.4.3-ubuntu@sha256:ca3f764fdc48cebdf22dd206f33ecb0795a9a7210eacd1b5c02204aebd78b223
|
image: grafana/grafana:12.4.4-ubuntu@sha256:df2e7ef5f32f771794cf76bad5f2bceac227036460a2cc269a9045e5662abc58
|
||||||
volumes:
|
volumes:
|
||||||
- grafana-data:/var/lib/grafana
|
- grafana-data:/var/lib/grafana
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ services:
|
|||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: immich_redis
|
container_name: immich_redis
|
||||||
image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9
|
image: docker.io/valkey/valkey:9@sha256:4963247afc4cd33c7d3b2d2816b9f7f8eeebab148d29056c2ca4d7cbc966f2d9
|
||||||
user: '1000:1000'
|
user: '1000:1000'
|
||||||
security_opt:
|
security_opt:
|
||||||
- no-new-privileges:true
|
- no-new-privileges:true
|
||||||
@@ -95,6 +95,3 @@ services:
|
|||||||
restart: always
|
restart: always
|
||||||
healthcheck:
|
healthcheck:
|
||||||
disable: false
|
disable: false
|
||||||
|
|
||||||
volumes:
|
|
||||||
model-cache:
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ services:
|
|||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: immich_redis
|
container_name: immich_redis
|
||||||
image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9
|
image: docker.io/valkey/valkey:9@sha256:4963247afc4cd33c7d3b2d2816b9f7f8eeebab148d29056c2ca4d7cbc966f2d9
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: redis-cli ping || exit 1
|
test: redis-cli ping || exit 1
|
||||||
restart: always
|
restart: always
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
24.15.0
|
|
||||||
@@ -26,6 +26,8 @@ For organizations seeking to resell Immich, we have established the following gu
|
|||||||
|
|
||||||
When in doubt or if you have an edge case scenario, we encourage you to contact us directly via email to discuss the use of our trademark. We can provide clear guidance on what is acceptable and what is not. You can reach out at: questions@immich.app
|
When in doubt or if you have an edge case scenario, we encourage you to contact us directly via email to discuss the use of our trademark. We can provide clear guidance on what is acceptable and what is not. You can reach out at: questions@immich.app
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## User
|
## User
|
||||||
|
|
||||||
### How can I reset the admin password?
|
### How can I reset the admin password?
|
||||||
@@ -36,6 +38,10 @@ The admin password can be reset by running the [reset-admin-password](/administr
|
|||||||
|
|
||||||
You can see the list of all users by running [list-users](/administration/server-commands.md) Command on the Immich-server.
|
You can see the list of all users by running [list-users](/administration/server-commands.md) Command on the Immich-server.
|
||||||
|
|
||||||
|
### How can I change my profile picture?
|
||||||
|
|
||||||
|
View a single photo, press the three dots in the top-right to show context menu, and select "Set as profile picture". In the pop-up, use your mouse scroll wheel to zoom in the picture until it completely fills the circle. Click and drag the picture to align it to your liking. Press "Save" to save your changes.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Mobile App
|
## Mobile App
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ running `apt install postgresql-NN-pgvector`, where `NN` is your Postgres versio
|
|||||||
You must install VectorChord into your instance of Postgres using their [instructions][vchord-install]. After installation, add `shared_preload_libraries = 'vchord.so'` to your `postgresql.conf`. If you already have some `shared_preload_libraries` set, you can separate each extension with a comma. For example, `shared_preload_libraries = 'pg_stat_statements, vchord.so'`.
|
You must install VectorChord into your instance of Postgres using their [instructions][vchord-install]. After installation, add `shared_preload_libraries = 'vchord.so'` to your `postgresql.conf`. If you already have some `shared_preload_libraries` set, you can separate each extension with a comma. For example, `shared_preload_libraries = 'pg_stat_statements, vchord.so'`.
|
||||||
|
|
||||||
:::note Supported versions
|
:::note Supported versions
|
||||||
Immich is known to work with Postgres versions `>= 14, < 19`.
|
Immich is known to work with Postgres versions `>= 14, < 20`.
|
||||||
|
|
||||||
VectorChord is known to work with pgvector versions `>= 0.7, < 0.9`.
|
VectorChord is known to work with pgvector versions `>= 0.7, < 0.9`.
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@ ALTER TABLE face_search ALTER COLUMN embedding SET DATA TYPE vector(512);
|
|||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Migration steps</summary>
|
<summary>Migration steps</summary>
|
||||||
1. Ensure you have at least 0.7.0 of pgvector installed. If it is below that, please upgrade it and run the SQL command `ALTER EXTENSION vector UPDATE;` using psql or your choice of database client
|
1. Ensure you have at least `0.7.0` of pgvector installed. If it is below that, please upgrade it and run the SQL command `ALTER EXTENSION vector UPDATE;` using psql or your choice of database client
|
||||||
2. Follow the Prerequisites to install VectorChord
|
2. Follow the Prerequisites to install VectorChord
|
||||||
3. If Immich does not have superuser permissions, run the SQL command `CREATE EXTENSION vchord CASCADE;`
|
3. If Immich does not have superuser permissions, run the SQL command `CREATE EXTENSION vchord CASCADE;`
|
||||||
4. Remove the `DB_VECTOR_EXTENSION=pgvector` environmental variable as it will make Immich still use pgvector if set
|
4. Remove the `DB_VECTOR_EXTENSION=pgvector` environmental variable as it will make Immich still use pgvector if set
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ services:
|
|||||||
traefik.enable: true
|
traefik.enable: true
|
||||||
# increase readingTimeouts for the entrypoint used here
|
# increase readingTimeouts for the entrypoint used here
|
||||||
traefik.http.routers.immich.entrypoints: websecure
|
traefik.http.routers.immich.entrypoints: websecure
|
||||||
traefik.http.routers.immich.rule: Host(`immich.your-domain.com`)
|
traefik.http.routers.immich.rule: Host(`immich.example.com`)
|
||||||
traefik.http.services.immich.loadbalancer.server.port: 2283
|
traefik.http.services.immich.loadbalancer.server.port: 2283
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,11 @@ The `immich-server` docker image comes preinstalled with an administrative CLI (
|
|||||||
| `enable-oauth-login` | Enable OAuth login |
|
| `enable-oauth-login` | Enable OAuth login |
|
||||||
| `disable-oauth-login` | Disable OAuth login |
|
| `disable-oauth-login` | Disable OAuth login |
|
||||||
| `list-users` | List Immich users |
|
| `list-users` | List Immich users |
|
||||||
|
| `grant-admin` | Grant admin privileges to a user (by email) |
|
||||||
|
| `revoke-admin` | Revoke admin privileges from a user (by email) |
|
||||||
| `version` | Print Immich version |
|
| `version` | Print Immich version |
|
||||||
| `change-media-location` | Change database file paths to align with a new media location |
|
| `change-media-location` | Change database file paths to align with a new media location |
|
||||||
|
| `schema-check` | Verify database migrations and check for schema drift |
|
||||||
|
|
||||||
## How to run a command
|
## How to run a command
|
||||||
|
|
||||||
@@ -87,7 +90,7 @@ immich-admin list-users
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
id: 'e65e6f88-2a30-4dbe-8dd9-1885f4889b53',
|
id: 'e65e6f88-2a30-4dbe-8dd9-1885f4889b53',
|
||||||
email: 'immich@example.com.com',
|
email: 'immich@example.com',
|
||||||
name: 'Immich Admin',
|
name: 'Immich Admin',
|
||||||
storageLabel: 'admin',
|
storageLabel: 'admin',
|
||||||
externalPath: null,
|
externalPath: null,
|
||||||
@@ -102,6 +105,22 @@ immich-admin list-users
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Grant Admin
|
||||||
|
|
||||||
|
```
|
||||||
|
immich-admin grant-admin
|
||||||
|
? Please enter the user email: user@example.com
|
||||||
|
Admin access has been granted to user@example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
Revoke Admin
|
||||||
|
|
||||||
|
```
|
||||||
|
immich-admin revoke-admin
|
||||||
|
? Please enter the user email: user@example.com
|
||||||
|
Admin access has been revoked from user@example.com
|
||||||
|
```
|
||||||
|
|
||||||
Print Immich Version
|
Print Immich Version
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -126,3 +145,12 @@ immich-admin change-media-location
|
|||||||
Database file paths updated successfully! 🎉
|
Database file paths updated successfully! 🎉
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Schema Check
|
||||||
|
|
||||||
|
```
|
||||||
|
immich-admin schema-check
|
||||||
|
Migrations are up to date
|
||||||
|
|
||||||
|
No schema drift detected
|
||||||
|
```
|
||||||
|
|||||||
+2
-2
@@ -7,7 +7,7 @@ Immich uses the [OpenAPI](https://swagger.io/specification/) standard to generat
|
|||||||
OpenAPI is used to generate the client (Typescript, Dart) SDK. `openapi-generator-cli` can be installed [here](https://openapi-generator.tech/docs/installation/). The generated SDK is based on the `immich-openapi-specs.json` file, which is autogenerated by the server **when running in development mode**. The `immich-openapi-specs.json` file can be modified with `@nestjs/swagger` decorators used or referenced by controller endpoints. See the [NestJS OpenAPI docs](https://docs.nestjs.com/openapi/types-and-parameters) for more info. When you add a new endpoint or modify an existing one, you must run the server in development mode and run the command below to update the client SDK.
|
OpenAPI is used to generate the client (Typescript, Dart) SDK. `openapi-generator-cli` can be installed [here](https://openapi-generator.tech/docs/installation/). The generated SDK is based on the `immich-openapi-specs.json` file, which is autogenerated by the server **when running in development mode**. The `immich-openapi-specs.json` file can be modified with `@nestjs/swagger` decorators used or referenced by controller endpoints. See the [NestJS OpenAPI docs](https://docs.nestjs.com/openapi/types-and-parameters) for more info. When you add a new endpoint or modify an existing one, you must run the server in development mode and run the command below to update the client SDK.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make open-api
|
mise open-api
|
||||||
```
|
```
|
||||||
|
|
||||||
You can find the generated client SDK in the `open-api/typescript-sdk/client` for Typescript SDK and `mobile/openapi` for Dart SDK.
|
You can find the generated client SDK in the `packages/sdk/client` for Typescript SDK and `mobile/openapi` for Dart SDK.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ After making any changes in the `server/src/schema`, a database migration need t
|
|||||||
1. Run the command
|
1. Run the command
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm run migrations:generate <migration-name>
|
mise //server:migrations generate <migration-name>
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Check if the migration file makes sense.
|
2. Check if the migration file makes sense.
|
||||||
@@ -18,7 +18,7 @@ The server will automatically detect `*.ts` file changes and restart. Part of th
|
|||||||
If you need to undo the most recently applied migration—for example, when developing or testing on schema changes—run:
|
If you need to undo the most recently applied migration—for example, when developing or testing on schema changes—run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm run migrations:revert
|
mise //server:migrations revert
|
||||||
```
|
```
|
||||||
|
|
||||||
This command rolls back the latest migration and brings the database schema back to its previous state.
|
This command rolls back the latest migration and brings the database schema back to its previous state.
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ When the Dev Container starts, it automatically:
|
|||||||
1. **Runs post-create script** (`container-server-post-create.sh`):
|
1. **Runs post-create script** (`container-server-post-create.sh`):
|
||||||
- Adjusts file permissions for the `node` user
|
- Adjusts file permissions for the `node` user
|
||||||
- Installs dependencies: `pnpm install` in all packages
|
- Installs dependencies: `pnpm install` in all packages
|
||||||
- Builds TypeScript SDK: `pnpm run build` in `open-api/typescript-sdk`
|
- Builds TypeScript SDK: `pnpm --filter @immich/sdk build`
|
||||||
|
|
||||||
2. **Starts development servers** via VS Code tasks:
|
2. **Starts development servers** via VS Code tasks:
|
||||||
- `Immich API Server (Nest)` - API server with hot-reloading on port 2283
|
- `Immich API Server (Nest)` - API server with hot-reloading on port 2283
|
||||||
@@ -218,7 +218,7 @@ When the Dev Container starts, it automatically:
|
|||||||
- Debug ports: 9230 (workers), 9231 (API)
|
- Debug ports: 9230 (workers), 9231 (API)
|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
The Dev Container setup replaces the `make dev` command from the traditional setup. All services start automatically when you open the container.
|
The Dev Container setup replaces the `mise dev` command from the traditional setup. All services start automatically when you open the container.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
### Accessing Services
|
### Accessing Services
|
||||||
@@ -243,8 +243,8 @@ To connect the mobile app to your Dev Container:
|
|||||||
|
|
||||||
- **Server code** (`/server`): Changes trigger automatic restart
|
- **Server code** (`/server`): Changes trigger automatic restart
|
||||||
- **Web code** (`/web`): Changes trigger hot module replacement
|
- **Web code** (`/web`): Changes trigger hot module replacement
|
||||||
- **Database migrations**: Run `pnpm run sync:sql` in the server directory
|
- **Database migrations**: Run `mise //:sql`
|
||||||
- **API changes**: Regenerate TypeScript SDK with `make open-api`
|
- **API changes**: Regenerate TypeScript SDK with `mise //:open-api`
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
@@ -252,85 +252,33 @@ To connect the mobile app to your Dev Container:
|
|||||||
|
|
||||||
The Dev Container supports multiple ways to run tests:
|
The Dev Container supports multiple ways to run tests:
|
||||||
|
|
||||||
#### Using Make Commands (Recommended)
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run tests for specific components
|
# Server
|
||||||
make test-server # Server unit tests
|
mise //server:test # unit tests
|
||||||
make test-web # Web unit tests
|
mise //server:test-medium # medium / integration tests
|
||||||
make test-e2e # End-to-end tests
|
|
||||||
make test-cli # CLI tests
|
|
||||||
|
|
||||||
# Run all tests
|
# Web
|
||||||
make test-all # Runs tests for all components
|
mise //web:test # unit tests
|
||||||
|
|
||||||
# Medium tests (integration tests)
|
# E2E
|
||||||
make test-medium-dev # End-to-end tests
|
mise //e2e:test # API tests
|
||||||
|
mise //e2e:test-web # web UI tests (Playwright)
|
||||||
|
|
||||||
|
# Run all checks for a component
|
||||||
|
mise //server:checklist
|
||||||
|
mise //web:checklist
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Using PNPM Directly
|
### Additional Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Server tests
|
|
||||||
cd /workspaces/immich/server
|
|
||||||
pnpm test # Run all tests
|
|
||||||
pnpm run test:medium # Medium tests (integration tests)
|
|
||||||
pnpm run test:watch # Watch mode
|
|
||||||
pnpm run test:cov # Coverage report
|
|
||||||
|
|
||||||
# Web tests
|
|
||||||
cd /workspaces/immich/web
|
|
||||||
pnpm test # Run all tests
|
|
||||||
pnpm run test:watch # Watch mode
|
|
||||||
|
|
||||||
# E2E tests
|
|
||||||
cd /workspaces/immich/e2e
|
|
||||||
pnpm run test # Run API tests
|
|
||||||
pnpm run test:web # Run web UI tests
|
|
||||||
```
|
|
||||||
|
|
||||||
### Code Quality Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Linting
|
|
||||||
make lint-server # Lint server code
|
|
||||||
make lint-web # Lint web code
|
|
||||||
make lint-all # Lint all components
|
|
||||||
|
|
||||||
# Formatting
|
|
||||||
make format-server # Format server code
|
|
||||||
make format-web # Format web code
|
|
||||||
make format-all # Format all code
|
|
||||||
|
|
||||||
# Type checking
|
|
||||||
make check-server # Type check server
|
|
||||||
make check-web # Type check web
|
|
||||||
make check-all # Check all components
|
|
||||||
|
|
||||||
# Complete hygiene check
|
|
||||||
make hygiene-all # Run lint, format, check, SQL sync, and audit
|
|
||||||
```
|
|
||||||
|
|
||||||
### Additional Make Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build commands
|
|
||||||
make build-server # Build server
|
|
||||||
make build-web # Build web app
|
|
||||||
make build-all # Build everything
|
|
||||||
|
|
||||||
# API generation
|
# API generation
|
||||||
make open-api # Generate OpenAPI specs
|
mise //:open-api # Generate OpenAPI specs
|
||||||
make open-api-typescript # Generate TypeScript SDK
|
mise //:open-api-typescript # Generate TypeScript SDK
|
||||||
make open-api-dart # Generate Dart SDK
|
mise //:open-api-dart # Generate Dart SDK
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
make sql # Sync database schema
|
mise //server:sql # Sync database schema
|
||||||
|
|
||||||
# Dependencies
|
|
||||||
make install-server # Install server dependencies
|
|
||||||
make install-web # Install web dependencies
|
|
||||||
make install-all # Install all dependencies
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Debugging
|
### Debugging
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ Our [GitHub Repository](https://github.com/immich-app/immich) is a [monorepo](ht
|
|||||||
| :------------------ | :------------------------------------------------------------------- |
|
| :------------------ | :------------------------------------------------------------------- |
|
||||||
| `.github/` | Github templates and action workflows |
|
| `.github/` | Github templates and action workflows |
|
||||||
| `.vscode/` | VSCode debug launch profiles |
|
| `.vscode/` | VSCode debug launch profiles |
|
||||||
| `cli/` | Source code for the work-in-progress CLI rewrite |
|
| `packages/cli` | Source code for the CLI |
|
||||||
|
| `packages/sdk` | Source code for the generated OpenAPI SDK |
|
||||||
| `docker/` | Docker compose resources for dev, test, production |
|
| `docker/` | Docker compose resources for dev, test, production |
|
||||||
| `design/` | Screenshots and logos for the README |
|
| `design/` | Screenshots and logos for the README |
|
||||||
| `docs/` | Source code for the [https://immich.app](https://immich.app) website |
|
| `docs/` | Source code for the [https://immich.app](https://immich.app) website |
|
||||||
|
|||||||
@@ -2,53 +2,74 @@
|
|||||||
|
|
||||||
A minimal devcontainer is supplied with this repository. All commands can be executed directly inside this container to avoid tedious installation of the environment.
|
A minimal devcontainer is supplied with this repository. All commands can be executed directly inside this container to avoid tedious installation of the environment.
|
||||||
:::warning
|
:::warning
|
||||||
The provided devcontainer isn't complete at the moment. At least all dockerized steps in the Makefile won't work (`make dev`, ....). Feel free to contribute!
|
The provided devcontainer isn't complete at the moment. At least all dockerized steps in the Makefile won't work (`mise dev`, ....). Feel free to contribute!
|
||||||
:::
|
:::
|
||||||
When contributing code through a pull request, please check the following:
|
When contributing code through a pull request, please check the following:
|
||||||
|
|
||||||
## Web Checks
|
## Web Checks
|
||||||
|
|
||||||
- [ ] `pnpm run lint` (linting via ESLint)
|
- [ ] `mise //web:lint` (linting via ESLint)
|
||||||
- [ ] `pnpm run format` (formatting via Prettier)
|
- [ ] `mise //web:format` (formatting via Prettier)
|
||||||
- [ ] `pnpm run check:svelte` (Type checking via SvelteKit)
|
- [ ] `mise //web:check-svelte` (type checking via SvelteKit)
|
||||||
- [ ] `pnpm run check:typescript` (check typescript)
|
- [ ] `mise //web:check-typescript` (type checking via `tsc`)
|
||||||
- [ ] `pnpm test` (unit tests)
|
- [ ] `mise //web:test` (unit tests)
|
||||||
|
|
||||||
:::tip AIO
|
:::tip AIO
|
||||||
Run all web checks with `pnpm run check:all`
|
Run all web checks with `mise //web:checklist`
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::tip Auto Fix
|
||||||
|
Use `mise //web:lint-fix` and `mise //web:format-fix` to automatically correct some issues.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
- [ ] `pnpm run format` (formatting via Prettier)
|
- [ ] `mise //docs:format` (formatting via Prettier)
|
||||||
- [ ] Update the `_redirects` file if you have renamed a page or removed it from the documentation.
|
- [ ] Update the `_redirects` file if you have renamed a page or removed it from the documentation.
|
||||||
|
|
||||||
|
:::tip Auto Fix
|
||||||
|
Use `mise //docs:format-fix` to automatically fix formatting.
|
||||||
|
:::
|
||||||
|
|
||||||
## Server Checks
|
## Server Checks
|
||||||
|
|
||||||
- [ ] `pnpm run lint` (linting via ESLint)
|
- [ ] `mise //server:lint` (linting via ESLint)
|
||||||
- [ ] `pnpm run format` (formatting via Prettier)
|
- [ ] `mise //server:format` (formatting via Prettier)
|
||||||
- [ ] `pnpm run check` (Type checking via `tsc`)
|
- [ ] `mise //server:check` (type checking via `tsc`)
|
||||||
- [ ] `pnpm test` (unit tests)
|
- [ ] `mise //server:test` (unit tests)
|
||||||
|
|
||||||
:::tip AIO
|
:::tip AIO
|
||||||
Run all server checks with `pnpm run check:all`
|
Run all server checks with `mise //server:checklist`
|
||||||
:::
|
:::
|
||||||
|
|
||||||
:::info Auto Fix
|
:::tip Auto Fix
|
||||||
You can use `pnpm run __:fix` to potentially correct some issues automatically for `pnpm run format` and `lint`.
|
Use `mise //server:lint-fix` and `mise //server:format-fix` to automatically correct some issues.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Mobile Checks
|
## Mobile Checklist
|
||||||
|
|
||||||
The following commands must be executed from within the mobile app directory of the codebase.
|
- [ ] `mise //mobile:codegen` (auto-generate files using build_runner)
|
||||||
|
- [ ] `mise //mobile:lint` (static analysis via Dart Analyzer and DCM)
|
||||||
|
- [ ] `mise //mobile:format` (formatting via Dart Formatter)
|
||||||
|
- [ ] `mise //mobile:test` (unit tests)
|
||||||
|
|
||||||
- [ ] `make build` (auto-generate files using build_runner)
|
:::tip
|
||||||
- [ ] `make analyze` (static analysis via Dart Analyzer and DCM)
|
Run all these commands at once with `mise //mobile:checklist`
|
||||||
- [ ] `make format` (formatting via Dart Formatter)
|
:::
|
||||||
- [ ] `make test` (unit tests)
|
|
||||||
|
|
||||||
:::info Auto Fix
|
:::tip Auto Fix
|
||||||
You can use `dart fix --apply` and `dcm fix lib` to potentially correct some issues automatically for `make analyze`.
|
You can use `mise //mobile:lint-fix` to potentially correct some issues automatically for `mise //mobile:lint`.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Machine Learning Checklist
|
||||||
|
|
||||||
|
- [ ] `mise //machine-learning:lint` (linting via ruff)
|
||||||
|
- [ ] `mise //machine-learning:format` (formatting via ruff)
|
||||||
|
- [ ] `mise //machine-learning:check` (type checking via mypy)
|
||||||
|
- [ ] `mise //machine-learning:test` (unit tests via pytest)
|
||||||
|
|
||||||
|
:::tip AIO
|
||||||
|
Run all machine learning checks with `mise //machine-learning:checklist`
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## OpenAPI
|
## OpenAPI
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ This environment includes the services below. Additional details are available i
|
|||||||
|
|
||||||
All the services are packaged to run as with single Docker Compose command.
|
All the services are packaged to run as with single Docker Compose command.
|
||||||
|
|
||||||
|
:::tip mise
|
||||||
|
[mise](https://mise.jdx.dev) is used throughout the project to manage tool versions and run tasks. [Install mise](https://mise.jdx.dev/installing-mise.html), then from the repo root run `mise trust` and `mise install` to get all required tools. Tasks for each service can be run from the repo root using `mise //namespace:task` (e.g. `mise //server:lint`). To list all available tasks, run `mise tasks ls --all`.
|
||||||
|
:::
|
||||||
|
|
||||||
### Server and web apps
|
### Server and web apps
|
||||||
|
|
||||||
1. Clone the project repo.
|
1. Clone the project repo.
|
||||||
@@ -41,7 +45,7 @@ All the services are packaged to run as with single Docker Compose command.
|
|||||||
5. From the root directory, run:
|
5. From the root directory, run:
|
||||||
|
|
||||||
```bash title="Start development server"
|
```bash title="Start development server"
|
||||||
make dev # required Makefile installed on the system.
|
mise dev
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Access the dev instance in your browser at http://localhost:3000, or connect via the mobile app.
|
5. Access the dev instance in your browser at http://localhost:3000, or connect via the mobile app.
|
||||||
@@ -56,22 +60,23 @@ You can access the web from `http://your-machine-ip:3000` or `http://localhost:3
|
|||||||
|
|
||||||
#### Connect web to a remote backend
|
#### Connect web to a remote backend
|
||||||
|
|
||||||
If you only want to do web development connected to an existing, remote backend, follow these steps:
|
If you only want to do web development connected to an existing, remote backend, run from the repo root:
|
||||||
|
|
||||||
1. Build the Immich SDK - `cd open-api/typescript-sdk && pnpm i && pnpm run build && cd -`
|
|
||||||
2. Enter the web directory - `cd web/`
|
|
||||||
3. Install web dependencies - `pnpm i`
|
|
||||||
4. Start the web development server
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
IMMICH_SERVER_URL=https://demo.immich.app/ pnpm run dev
|
IMMICH_SERVER_URL=https://demo.immich.app/ mise //web:start
|
||||||
|
```
|
||||||
|
|
||||||
|
This will install all dependencies (including the SDK) and start the dev server in one step. To connect to the hosted demo server specifically, use the shorthand:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mise //web:start-demo
|
||||||
```
|
```
|
||||||
|
|
||||||
If you're using PowerShell on Windows you may need to set the env var separately like so:
|
If you're using PowerShell on Windows you may need to set the env var separately like so:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
$env:IMMICH_SERVER_URL = "https://demo.immich.app/"
|
$env:IMMICH_SERVER_URL = "https://demo.immich.app/"
|
||||||
pnpm run dev
|
mise //web:start
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `@immich/ui`
|
#### `@immich/ui`
|
||||||
@@ -83,31 +88,45 @@ To see local changes to `@immich/ui` in Immich, do the following:
|
|||||||
3. Uncomment the corresponding volume in web service of the `docker/docker-compose.dev.yml` file (`../../ui:/usr/src/ui`)
|
3. Uncomment the corresponding volume in web service of the `docker/docker-compose.dev.yml` file (`../../ui:/usr/src/ui`)
|
||||||
4. Uncomment the corresponding alias in the `web/vite.config.ts` file (`'@immich/ui': path.resolve(\_\_dirname, '../../ui/packages/ui')`)
|
4. Uncomment the corresponding alias in the `web/vite.config.ts` file (`'@immich/ui': path.resolve(\_\_dirname, '../../ui/packages/ui')`)
|
||||||
5. Uncomment the import statement in `web/src/app.css` file `@import '../../../ui/packages/ui/dist/theme/default.css';` and comment out `@import '@immich/ui/theme/default.css';`
|
5. Uncomment the import statement in `web/src/app.css` file `@import '../../../ui/packages/ui/dist/theme/default.css';` and comment out `@import '@immich/ui/theme/default.css';`
|
||||||
6. Start up the stack via `make dev`
|
6. Start up the stack via `mise dev`
|
||||||
7. After making changes in `@immich/ui`, rebuild it (`pnpm run build`)
|
7. After making changes in `@immich/ui`, rebuild it (`pnpm run build`)
|
||||||
|
|
||||||
### Mobile app
|
### Mobile app
|
||||||
|
|
||||||
#### Setup
|
#### Setup
|
||||||
|
|
||||||
1. [Install mise](https://mise.jdx.dev/installing-mise.html).
|
1. Run `mise //mobile:install` to install Flutter dependencies.
|
||||||
2. Change to the immich (root) directory and trust the mise config with `mise trust`.
|
2. Run `mise //mobile:translation` to generate the translation file.
|
||||||
3. Install tools with mise: `mise install`.
|
3. Change to the `mobile/` directory and run `flutter run` to start the app.
|
||||||
4. Change to the `mobile/` directory.
|
|
||||||
5. Run `flutter pub get` to install the dependencies.
|
|
||||||
6. Run `make translation` to generate the translation file.
|
|
||||||
7. Run `flutter run` to start the app.
|
|
||||||
|
|
||||||
#### Translation
|
#### Translation
|
||||||
|
|
||||||
To add a new translation text, enter the key-value pair in the `i18n/en.json` in the root of the immich project. Then, from the `mobile/` directory, run
|
To add a new translation text, enter the key-value pair in the `i18n/en.json` in the root of the immich project. Then run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make translation
|
mise //mobile:translation
|
||||||
```
|
```
|
||||||
|
|
||||||
The mobile app asks you what backend to connect to. You can utilize the demo backend (https://demo.immich.app/) if you don't need to change server code or upload photos. Alternatively, you can run the server yourself per the instructions above.
|
The mobile app asks you what backend to connect to. You can utilize the demo backend (https://demo.immich.app/) if you don't need to change server code or upload photos. Alternatively, you can run the server yourself per the instructions above.
|
||||||
|
|
||||||
|
#### UI components and widget previews
|
||||||
|
|
||||||
|
Shared design-system widgets (buttons, inputs, forms) live in the
|
||||||
|
[`immich_ui` package](https://github.com/immich-app/immich/tree/main/mobile/packages/ui/)
|
||||||
|
under `mobile/packages/ui/`. Components are defined in `lib/src/components/`
|
||||||
|
and have matching previews in `lib/src/previews/`.
|
||||||
|
|
||||||
|
To inspect a component in isolation with a light/dark toggle and hot reload,
|
||||||
|
launch [Flutter's Widget Previewer](https://docs.flutter.dev/tools/widget-previewer):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd mobile/packages/ui
|
||||||
|
flutter widget-preview start
|
||||||
|
```
|
||||||
|
|
||||||
|
In VS Code or Android Studio with the Flutter plugin, the previewer
|
||||||
|
auto-starts when you open the **Flutter Widget Preview** tab in the sidebar.
|
||||||
|
|
||||||
## IDE setup
|
## IDE setup
|
||||||
|
|
||||||
### Lint / format extensions
|
### Lint / format extensions
|
||||||
|
|||||||
@@ -4,28 +4,26 @@
|
|||||||
|
|
||||||
### Unit tests
|
### Unit tests
|
||||||
|
|
||||||
Unit are run by calling `pnpm run test` from the `server/` directory.
|
Unit tests are run with `mise //server:test`.
|
||||||
You need to run `pnpm install` (in `server/`) before _once_.
|
You need to run `mise //server:install` before _once_.
|
||||||
|
|
||||||
### End to end tests
|
### End to end tests
|
||||||
|
|
||||||
The e2e tests can be run by first starting up a test production environment via:
|
The e2e tests can be run by first starting up a test production environment via:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make e2e
|
mise e2e
|
||||||
```
|
```
|
||||||
|
|
||||||
Before you can run the tests, you need to run the following commands _once_:
|
Before you can run the tests, you need to run the following commands _once_:
|
||||||
|
|
||||||
- `pnpm install` (in `e2e/`)
|
- `mise //e2e:ci-setup` (installs e2e, SDK, and CLI dependencies)
|
||||||
- `pnpm run build` (in `cli/`)
|
- `mise //:open-api`
|
||||||
- `make open-api` (in the project root `/`)
|
|
||||||
|
|
||||||
Once the test environment is running, the e2e tests can be run via:
|
Once the test environment is running, the e2e tests can be run via:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd e2e/
|
mise //e2e:test
|
||||||
pnpm test
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The tests check various things including:
|
The tests check various things including:
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ import MobileAppBackup from '/docs/partials/_mobile-app-backup.md';
|
|||||||
:::info Android verification
|
:::info Android verification
|
||||||
Below are the SHA-256 fingerprints for the certificates signing the android applications.
|
Below are the SHA-256 fingerprints for the certificates signing the android applications.
|
||||||
|
|
||||||
- Playstore / Github releases:
|
- Google Play releases:
|
||||||
|
`5A:22:C1:83:47:54:05:F5:49:C4:EB:9F:B2:6C:2E:93:A3:EF:9C:57:66:15:0A:7A:F3:8C:8D:3F:E5:15:CC:D6`
|
||||||
|
- GitHub releases:
|
||||||
`86:C5:C4:55:DF:AF:49:85:92:3A:8F:35:AD:B3:1D:0C:9E:0B:95:7D:7F:94:C2:D2:AF:6A:24:38:AA:96:00:20`
|
`86:C5:C4:55:DF:AF:49:85:92:3A:8F:35:AD:B3:1D:0C:9E:0B:95:7D:7F:94:C2:D2:AF:6A:24:38:AA:96:00:20`
|
||||||
- F-Droid releases:
|
- F-Droid releases:
|
||||||
`FA:8B:43:95:F4:A6:47:71:A0:53:D1:C7:57:73:5F:A2:30:13:74:F5:3D:58:0D:D1:75:AA:F7:A1:35:72:9C:BF`
|
`FA:8B:43:95:F4:A6:47:71:A0:53:D1:C7:57:73:5F:A2:30:13:74:F5:3D:58:0D:D1:75:AA:F7:A1:35:72:9C:BF`
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "8888:80"
|
- "8888:80"
|
||||||
environment:
|
environment:
|
||||||
PGADMIN_DEFAULT_EMAIL: user-name@domain-name.com
|
PGADMIN_DEFAULT_EMAIL: admin@example.com
|
||||||
PGADMIN_DEFAULT_PASSWORD: strong-password
|
PGADMIN_DEFAULT_PASSWORD: strong-password
|
||||||
volumes:
|
volumes:
|
||||||
- pgadmin-data:/var/lib/pgadmin
|
- pgadmin-data:/var/lib/pgadmin
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ The default configuration looks like this:
|
|||||||
},
|
},
|
||||||
"ffmpeg": {
|
"ffmpeg": {
|
||||||
"accel": "disabled",
|
"accel": "disabled",
|
||||||
"accelDecode": false,
|
"accelDecode": true,
|
||||||
"acceptedAudioCodecs": ["aac", "mp3", "opus"],
|
"acceptedAudioCodecs": ["aac", "mp3", "opus"],
|
||||||
"acceptedContainers": ["mov", "ogg", "webm"],
|
"acceptedContainers": ["mov", "ogg", "webm"],
|
||||||
"acceptedVideoCodecs": ["h264"],
|
"acceptedVideoCodecs": ["h264"],
|
||||||
@@ -264,4 +264,4 @@ volumes:
|
|||||||
- ./configuration.yml:${IMMICH_CONFIG_FILE}
|
- ./configuration.yml:${IMMICH_CONFIG_FILE}
|
||||||
```
|
```
|
||||||
|
|
||||||
::
|
:::
|
||||||
|
|||||||
@@ -154,33 +154,33 @@ Redis (Sentinel) URL example JSON before encoding:
|
|||||||
|
|
||||||
## Machine Learning
|
## Machine Learning
|
||||||
|
|
||||||
| Variable | Description | Default | Containers |
|
| Variable | Description | Default | Containers |
|
||||||
| :---------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------------------------: | :--------------- |
|
| :---------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------: | :--------------- |
|
||||||
| `MACHINE_LEARNING_MODEL_TTL` | Inactivity time (s) before a model is unloaded (disabled if \<= 0) | `300` | machine learning |
|
| `MACHINE_LEARNING_MODEL_TTL` | Inactivity time (s) before a model is unloaded (disabled if \<= 0) | `300` | machine learning |
|
||||||
| `MACHINE_LEARNING_MODEL_TTL_POLL_S` | Interval (s) between checks for the model TTL (disabled if \<= 0) | `10` | machine learning |
|
| `MACHINE_LEARNING_MODEL_TTL_POLL_S` | Interval (s) between checks for the model TTL (disabled if \<= 0) | `10` | machine learning |
|
||||||
| `MACHINE_LEARNING_CACHE_FOLDER` | Directory where models are downloaded | `/cache` | machine learning |
|
| `MACHINE_LEARNING_CACHE_FOLDER` | Directory where models are downloaded | `/cache` | machine learning |
|
||||||
| `MACHINE_LEARNING_REQUEST_THREADS`<sup>\*1</sup> | Thread count of the request thread pool (disabled if \<= 0) | number of CPU cores | machine learning |
|
| `MACHINE_LEARNING_REQUEST_THREADS`<sup>\*1</sup> | Thread count of the request thread pool (disabled if \<= 0) | number of CPU cores | machine learning |
|
||||||
| `MACHINE_LEARNING_MODEL_INTER_OP_THREADS` | Number of parallel model operations | `1` | machine learning |
|
| `MACHINE_LEARNING_MODEL_INTER_OP_THREADS` | Number of parallel model operations | `1` | machine learning |
|
||||||
| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning |
|
| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning |
|
||||||
| `MACHINE_LEARNING_WORKERS`<sup>\*2</sup> | Number of worker processes to spawn | `1` | machine learning |
|
| `MACHINE_LEARNING_WORKERS`<sup>\*2</sup> | Number of worker processes to spawn | `1` | machine learning |
|
||||||
| `MACHINE_LEARNING_HTTP_KEEPALIVE_TIMEOUT_S`<sup>\*3</sup> | HTTP Keep-alive time in seconds | `2` | machine learning |
|
| `MACHINE_LEARNING_HTTP_KEEPALIVE_TIMEOUT_S`<sup>\*3</sup> | HTTP Keep-alive time in seconds | `2` | machine learning |
|
||||||
| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `120` (`300` if using OpenVINO) | machine learning |
|
| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `300` (`900` if using ROCm) | machine learning |
|
||||||
| `MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL` | Comma-separated list of (textual) CLIP model(s) to preload and cache | | machine learning |
|
| `MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL` | Comma-separated list of (textual) CLIP model(s) to preload and cache | | machine learning |
|
||||||
| `MACHINE_LEARNING_PRELOAD__CLIP__VISUAL` | Comma-separated list of (visual) CLIP model(s) to preload and cache | | machine learning |
|
| `MACHINE_LEARNING_PRELOAD__CLIP__VISUAL` | Comma-separated list of (visual) CLIP model(s) to preload and cache | | machine learning |
|
||||||
| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION` | Comma-separated list of (recognition) facial recognition model(s) to preload and cache | | machine learning |
|
| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION` | Comma-separated list of (recognition) facial recognition model(s) to preload and cache | | machine learning |
|
||||||
| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION` | Comma-separated list of (detection) facial recognition model(s) to preload and cache | | machine learning |
|
| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION` | Comma-separated list of (detection) facial recognition model(s) to preload and cache | | machine learning |
|
||||||
| `MACHINE_LEARNING_PRELOAD__OCR__RECOGNITION` | Comma-separated list of (recognition) OCR model(s) to preload and cache | | machine learning |
|
| `MACHINE_LEARNING_PRELOAD__OCR__RECOGNITION` | Comma-separated list of (recognition) OCR model(s) to preload and cache | | machine learning |
|
||||||
| `MACHINE_LEARNING_PRELOAD__OCR__DETECTION` | Comma-separated list of (detection) OCR model(s) to preload and cache | | machine learning |
|
| `MACHINE_LEARNING_PRELOAD__OCR__DETECTION` | Comma-separated list of (detection) OCR model(s) to preload and cache | | machine learning |
|
||||||
| `MACHINE_LEARNING_ANN` | Enable ARM-NN hardware acceleration if supported | `True` | machine learning |
|
| `MACHINE_LEARNING_ANN` | Enable ARM-NN hardware acceleration if supported | `True` | machine learning |
|
||||||
| `MACHINE_LEARNING_ANN_FP16_TURBO` | Execute operations in FP16 precision: increasing speed, reducing precision (applies only to ARM-NN) | `False` | machine learning |
|
| `MACHINE_LEARNING_ANN_FP16_TURBO` | Execute operations in FP16 precision: increasing speed, reducing precision (applies only to ARM-NN) | `False` | machine learning |
|
||||||
| `MACHINE_LEARNING_ANN_TUNING_LEVEL` | ARM-NN GPU tuning level (1: rapid, 2: normal, 3: exhaustive) | `2` | machine learning |
|
| `MACHINE_LEARNING_ANN_TUNING_LEVEL` | ARM-NN GPU tuning level (1: rapid, 2: normal, 3: exhaustive) | `2` | machine learning |
|
||||||
| `MACHINE_LEARNING_DEVICE_IDS`<sup>\*4</sup> | Device IDs to use in multi-GPU environments | `0` | machine learning |
|
| `MACHINE_LEARNING_DEVICE_IDS`<sup>\*4</sup> | Device IDs to use in multi-GPU environments | `0` | machine learning |
|
||||||
| `MACHINE_LEARNING_MAX_BATCH_SIZE__FACIAL_RECOGNITION` | Set the maximum number of faces that will be processed at once by the facial recognition model | None (`1` if using OpenVINO) | machine learning |
|
| `MACHINE_LEARNING_MAX_BATCH_SIZE__FACIAL_RECOGNITION` | Set the maximum number of faces that will be processed at once by the facial recognition model | None (`1` if using OpenVINO) | machine learning |
|
||||||
| `MACHINE_LEARNING_MAX_BATCH_SIZE__OCR` | Set the maximum number of boxes that will be processed at once by the OCR model | `6` | machine learning |
|
| `MACHINE_LEARNING_MAX_BATCH_SIZE__OCR` | Set the maximum number of boxes that will be processed at once by the OCR model | `6` | machine learning |
|
||||||
| `MACHINE_LEARNING_RKNN` | Enable RKNN hardware acceleration if supported | `True` | machine learning |
|
| `MACHINE_LEARNING_RKNN` | Enable RKNN hardware acceleration if supported | `True` | machine learning |
|
||||||
| `MACHINE_LEARNING_RKNN_THREADS` | How many threads of RKNN runtime should be spun up while inferencing. | `1` | machine learning |
|
| `MACHINE_LEARNING_RKNN_THREADS` | How many threads of RKNN runtime should be spun up while inferencing. | `1` | machine learning |
|
||||||
| `MACHINE_LEARNING_MODEL_ARENA` | Pre-allocates CPU memory to avoid memory fragmentation | true | machine learning |
|
| `MACHINE_LEARNING_MODEL_ARENA` | Pre-allocates CPU memory to avoid memory fragmentation | true | machine learning |
|
||||||
| `MACHINE_LEARNING_OPENVINO_PRECISION` | If set to FP16, uses half-precision floating-point operations for faster inference with reduced accuracy (one of [`FP16`, `FP32`], applies only to OpenVINO) | `FP32` | machine learning |
|
| `MACHINE_LEARNING_OPENVINO_PRECISION` | If set to FP16, uses half-precision floating-point operations for faster inference with reduced accuracy (one of [`FP16`, `FP32`], applies only to OpenVINO) | `FP32` | machine learning |
|
||||||
|
|
||||||
\*1: It is recommended to begin with this parameter when changing the concurrency levels of the machine learning service and then tune the other ones.
|
\*1: It is recommended to begin with this parameter when changing the concurrency levels of the machine learning service and then tune the other ones.
|
||||||
|
|
||||||
|
|||||||
@@ -20,9 +20,11 @@ Hardware and software requirements for Immich:
|
|||||||
- **RAM**: Minimum 6GB, recommended 8GB.
|
- **RAM**: Minimum 6GB, recommended 8GB.
|
||||||
- **CPU**: Minimum 2 cores, recommended 4 cores.
|
- **CPU**: Minimum 2 cores, recommended 4 cores.
|
||||||
- Immich runs on the `amd64` and `arm64` platforms.
|
- Immich runs on the `amd64` and `arm64` platforms.
|
||||||
Since `v2.6`, the machine learning container on `amd64` requires the `>= x86-64-v2` [microarchitecture level](https://en.wikipedia.org/wiki/X86-64#Microarchitecture_levels).
|
Since `v3`, the machine learning container on `amd64` requires the `>= x86-64-v2` [microarchitecture level](https://en.wikipedia.org/wiki/X86-64#Microarchitecture_levels).
|
||||||
Most CPUs released since ~2012 support this microarchitecture.
|
Most CPUs released since ~2012 support this microarchitecture.
|
||||||
If you are using a virtual machine, ensure you have selected a [supported microarchitecture](https://pve.proxmox.com/pve-docs/chapter-qm.html#_qemu_cpu_types).
|
If you are using a virtual machine, ensure you have selected a [supported microarchitecture](https://pve.proxmox.com/pve-docs/chapter-qm.html#_qemu_cpu_types).
|
||||||
|
If you are unable to support this instruction set, the last version to support `x86-64-v1` is `v2.7.5`.
|
||||||
|
Note that this release is no longer supported, and you must run a matching `immich-server` version.
|
||||||
- **Storage**: Recommended Unix-compatible filesystem (EXT4, ZFS, APFS, etc.) with support for user/group ownership and permissions.
|
- **Storage**: Recommended Unix-compatible filesystem (EXT4, ZFS, APFS, etc.) with support for user/group ownership and permissions.
|
||||||
- The generation of thumbnails and transcoded video can increase the size of the photo library by 10-20% on average.
|
- The generation of thumbnails and transcoded video can increase the size of the photo library by 10-20% on average.
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ Scroll to the bottom of the "**Details**" section and find the `IP Address` list
|
|||||||
|
|
||||||
## Step 4 - Configure Firewall Settings
|
## Step 4 - Configure Firewall Settings
|
||||||
|
|
||||||
Once your project completes the build process, your containers will start. In order to be able to access Immich from your browser, you need to configure the firewall settings for your Synology NAS.
|
Once your project completes the build process, your containers will start. In order to be able to access Immich from your browser, you need to configure the firewall settings for your Synology NAS to allow communication between the Immich containers.
|
||||||
|
|
||||||
Open "**Control Panel**" on your Synology NAS, and select "**Security**". Navigate to "**Firewall**"
|
Open "**Control Panel**" on your Synology NAS, and select "**Security**". Navigate to "**Firewall**"
|
||||||
|
|
||||||
@@ -74,6 +74,7 @@ Read the [Post Installation](/install/post-install.mdx) steps and [upgrade instr
|
|||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Updating Immich using Container Manager</summary>
|
<summary>Updating Immich using Container Manager</summary>
|
||||||
|
|
||||||
Check the post installation and upgrade instructions at the links above before proceeding with this section.
|
Check the post installation and upgrade instructions at the links above before proceeding with this section.
|
||||||
|
|
||||||
## Step 1. Backup
|
## Step 1. Backup
|
||||||
@@ -110,7 +111,7 @@ Go to **Project**, select **Action** then **Build**. This will download, unpack,
|
|||||||
|
|
||||||
## Step 5. Update firewall rule
|
## Step 5. Update firewall rule
|
||||||
|
|
||||||
The default behavior is to automatically start the containers once installed. If `immich_server` runs for a few seconds and then stops, it may be because the firewall rule no longer matches the server IP address.
|
Without a fixed subnet, the default behavior is to automatically start the containers once installed. If `immich_server` runs for a few seconds and then stops, it may be because the firewall rule no longer matches the server IP address.
|
||||||
|
|
||||||
Go to the **Container** section. Click on `immich_server` and scroll down on **General** to find the IP address.
|
Go to the **Container** section. Click on `immich_server` and scroll down on **General** to find the IP address.
|
||||||

|

|
||||||
@@ -123,4 +124,67 @@ In this example, the IP addresses mismatch and the firewall rule needs to be edi
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
To prevent future firewall issues, you may set a fixed subnet. [See Set Fixed Subnet](#set-fixed-subnet) for instructions.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details id="set-fixed-subnet">
|
||||||
|
<summary>Set Fixed Subnet</summary>
|
||||||
|
|
||||||
|
Docker by default assigns dynamic subnets to bridge networks which can change when rebuilding containers and can cause firewall rules to break. To avoid this, define a fixed subnet in your `docker-compose.yml`:
|
||||||
|
|
||||||
|
## Step 1. Determine current subnet
|
||||||
|
|
||||||
|
Go to the **Container** section. Click on `immich_server` and scroll down on **General** to find the IP address.
|
||||||
|

|
||||||
|
|
||||||
|
## Step 2. Add network configuration
|
||||||
|
|
||||||
|
Add the following network configuration at the end of your `docker-compose.yml` file:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
networks:
|
||||||
|
immich-network:
|
||||||
|
driver: bridge
|
||||||
|
ipam:
|
||||||
|
config:
|
||||||
|
- subnet: 172.20.0.0/16
|
||||||
|
gateway: 172.20.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
If your docker container is running on a different subnet then update accordingly.
|
||||||
|
|
||||||
|
## Step 3. Add network to each service
|
||||||
|
|
||||||
|
Add the network to each service (immich-server, immich-machine-learning, redis, database):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
immich-server:
|
||||||
|
# other config options
|
||||||
|
networks:
|
||||||
|
- immich-network
|
||||||
|
|
||||||
|
immich-machine-learning:
|
||||||
|
# other config options
|
||||||
|
networks:
|
||||||
|
- immich-network
|
||||||
|
|
||||||
|
redis:
|
||||||
|
# other config options
|
||||||
|
networks:
|
||||||
|
- immich-network
|
||||||
|
|
||||||
|
database:
|
||||||
|
# other config options
|
||||||
|
networks:
|
||||||
|
- immich-network
|
||||||
|
```
|
||||||
|
|
||||||
|
Save your changes. Synology will ask if you want to save changes only or rebuild containers. Select rebuild containers.
|
||||||
|
|
||||||
|
## Step 4. Update Firewall Rules, if necessary
|
||||||
|
|
||||||
|
If your firewall rules were not already set for this subnet, the firewall rules will need to be updated. See [Step 4 - Configure Firewall Settings](#step-4---configure-firewall-settings).
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ const config = {
|
|||||||
url: 'https://docs.immich.app',
|
url: 'https://docs.immich.app',
|
||||||
baseUrl: '/',
|
baseUrl: '/',
|
||||||
onBrokenLinks: 'throw',
|
onBrokenLinks: 'throw',
|
||||||
onBrokenMarkdownLinks: 'warn',
|
|
||||||
favicon: 'img/favicon.png',
|
favicon: 'img/favicon.png',
|
||||||
|
|
||||||
// GitHub pages deployment config.
|
// GitHub pages deployment config.
|
||||||
@@ -29,6 +28,9 @@ const config = {
|
|||||||
// Mermaid diagrams
|
// Mermaid diagrams
|
||||||
markdown: {
|
markdown: {
|
||||||
mermaid: true,
|
mermaid: true,
|
||||||
|
hooks: {
|
||||||
|
onBrokenMarkdownLinks: 'warn',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
themes: ['@docusaurus/theme-mermaid'],
|
themes: ['@docusaurus/theme-mermaid'],
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html
|
||||||
|
|
||||||
|
[[tools.wrangler]]
|
||||||
|
version = "4.98.0"
|
||||||
|
backend = "npm:wrangler"
|
||||||
|
|
||||||
|
[tools.wrangler.options]
|
||||||
|
allow_builds = '["esbuild", "sharp", "workerd"]'
|
||||||
+2
-2
@@ -3,7 +3,7 @@ run = "pnpm install --filter documentation --frozen-lockfile"
|
|||||||
|
|
||||||
[tasks.start]
|
[tasks.start]
|
||||||
env._.path = "./node_modules/.bin"
|
env._.path = "./node_modules/.bin"
|
||||||
run = "docusaurus --port 3005"
|
run = "docusaurus start --port 3005"
|
||||||
|
|
||||||
[tasks.build]
|
[tasks.build]
|
||||||
env._.path = "./node_modules/.bin"
|
env._.path = "./node_modules/.bin"
|
||||||
@@ -28,4 +28,4 @@ run = "prettier --write ."
|
|||||||
run = "wrangler pages deploy build --project-name=${PROJECT_NAME} --branch=${BRANCH_NAME}"
|
run = "wrangler pages deploy build --project-name=${PROJECT_NAME} --branch=${BRANCH_NAME}"
|
||||||
|
|
||||||
[tools]
|
[tools]
|
||||||
wrangler = "4.66.0"
|
wrangler = "4.98.0"
|
||||||
|
|||||||
@@ -56,8 +56,5 @@
|
|||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
},
|
|
||||||
"volta": {
|
|
||||||
"node": "24.15.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+4
@@ -1,4 +1,8 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"label": "v3.0.0-rc.1",
|
||||||
|
"url": "https://docs.v3.0.0-rc.1.archive.immich.app"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "v2.7.5",
|
"label": "v2.7.5",
|
||||||
"url": "https://docs.v2.7.5.archive.immich.app"
|
"url": "https://docs.v2.7.5.archive.immich.app"
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
FROM node:24.1.0-alpine3.20@sha256:8fe019e0d57dbdce5f5c27c0b63d2775cf34b00e3755a7dea969802d7e0c2b25
|
|
||||||
RUN corepack enable
|
|
||||||
ADD package.json *.ts ./
|
|
||||||
RUN pnpm install
|
|
||||||
EXPOSE 2286
|
|
||||||
CMD ["pnpm", "run", "start"]
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
24.15.0
|
|
||||||
@@ -83,9 +83,7 @@ volumes:
|
|||||||
model_cache:
|
model_cache:
|
||||||
prometheus_data:
|
prometheus_data:
|
||||||
grafana_data:
|
grafana_data:
|
||||||
pnpm_cache:
|
build_cache:
|
||||||
pnpm_store_server:
|
|
||||||
pnpm_store_web:
|
|
||||||
server_node_modules:
|
server_node_modules:
|
||||||
web_node_modules:
|
web_node_modules:
|
||||||
github_node_modules:
|
github_node_modules:
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ services:
|
|||||||
e2e-auth-server:
|
e2e-auth-server:
|
||||||
container_name: immich-e2e-auth-server
|
container_name: immich-e2e-auth-server
|
||||||
build:
|
build:
|
||||||
context: ../e2e-auth-server
|
context: ../
|
||||||
|
dockerfile: packages/e2e-auth-server/Dockerfile
|
||||||
ports:
|
ports:
|
||||||
- 2286:2286
|
- 2286:2286
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@ services:
|
|||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: immich-e2e-redis
|
container_name: immich-e2e-redis
|
||||||
image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9
|
image: docker.io/valkey/valkey:9@sha256:4963247afc4cd33c7d3b2d2816b9f7f8eeebab148d29056c2ca4d7cbc966f2d9
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: redis-cli ping || exit 1
|
test: redis-cli ping || exit 1
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,21 @@
|
|||||||
[tasks.install]
|
[tasks.install]
|
||||||
run = "pnpm install --filter immich-e2e --frozen-lockfile"
|
run = "pnpm install --filter immich-e2e --frozen-lockfile"
|
||||||
|
|
||||||
|
[tasks.build]
|
||||||
|
dir = "{{ config_root }}"
|
||||||
|
run = "docker compose build"
|
||||||
|
|
||||||
[tasks.test]
|
[tasks.test]
|
||||||
|
depends = ["//e2e:build", "//e2e:ci-setup"]
|
||||||
env._.path = "./node_modules/.bin"
|
env._.path = "./node_modules/.bin"
|
||||||
run = "vitest --run"
|
run = "vitest --run"
|
||||||
|
|
||||||
|
[tasks.playwright-install]
|
||||||
|
env._.path = "./node_modules/.bin"
|
||||||
|
run = "playwright install"
|
||||||
|
|
||||||
[tasks."test-web"]
|
[tasks."test-web"]
|
||||||
|
depends = ["//e2e:build", "//e2e:ci-setup", "//e2e:playwright-install"]
|
||||||
env._.path = "./node_modules/.bin"
|
env._.path = "./node_modules/.bin"
|
||||||
run = "playwright test"
|
run = "playwright test"
|
||||||
|
|
||||||
@@ -27,3 +37,23 @@ run = { task = "lint --fix" }
|
|||||||
[tasks.check]
|
[tasks.check]
|
||||||
env._.path = "./node_modules/.bin"
|
env._.path = "./node_modules/.bin"
|
||||||
run = "tsc --noEmit"
|
run = "tsc --noEmit"
|
||||||
|
|
||||||
|
|
||||||
|
[tasks.ci-setup]
|
||||||
|
depends = [
|
||||||
|
"//:sdk:install",
|
||||||
|
"//:sdk:build",
|
||||||
|
"//packages/cli:install",
|
||||||
|
"//packages/cli:build",
|
||||||
|
]
|
||||||
|
run = { task = ":install" }
|
||||||
|
|
||||||
|
|
||||||
|
[tasks.ci-unit]
|
||||||
|
depends = ["//:sdk:install", "//:sdk:build"]
|
||||||
|
run = [
|
||||||
|
{ task = ":install" },
|
||||||
|
{ task = ":format" },
|
||||||
|
{ task = ":lint" },
|
||||||
|
{ task = ":check" },
|
||||||
|
]
|
||||||
|
|||||||
+2
-6
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-e2e",
|
"name": "immich-e2e",
|
||||||
"version": "2.7.5",
|
"version": "3.0.0-rc.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
"@playwright/test": "^1.44.1",
|
"@playwright/test": "^1.44.1",
|
||||||
"@socket.io/component-emitter": "^3.1.2",
|
"@socket.io/component-emitter": "^3.1.2",
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
"@types/node": "^24.12.2",
|
"@types/node": "^24.13.2",
|
||||||
"@types/pg": "^8.15.1",
|
"@types/pg": "^8.15.1",
|
||||||
"@types/pngjs": "^6.0.4",
|
"@types/pngjs": "^6.0.4",
|
||||||
"@types/supertest": "^7.0.0",
|
"@types/supertest": "^7.0.0",
|
||||||
@@ -54,10 +54,6 @@
|
|||||||
"typescript": "^6.0.0",
|
"typescript": "^6.0.0",
|
||||||
"typescript-eslint": "^8.28.0",
|
"typescript-eslint": "^8.28.0",
|
||||||
"utimes": "^5.2.1",
|
"utimes": "^5.2.1",
|
||||||
"vite-tsconfig-paths": "^6.1.1",
|
|
||||||
"vitest": "^4.0.0"
|
"vitest": "^4.0.0"
|
||||||
},
|
|
||||||
"volta": {
|
|
||||||
"node": "24.15.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { LoginResponseDto, ManualJobName } from '@immich/sdk';
|
|||||||
import { errorDto } from 'src/responses';
|
import { errorDto } from 'src/responses';
|
||||||
import { app, utils } from 'src/utils';
|
import { app, utils } from 'src/utils';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
describe('/admin/database-backups', () => {
|
describe('/admin/database-backups', () => {
|
||||||
let cookie: string | undefined;
|
let cookie: string | undefined;
|
||||||
@@ -13,6 +13,9 @@ describe('/admin/database-backups', () => {
|
|||||||
admin = await utils.adminSetup({
|
admin = await utils.adminSetup({
|
||||||
onboarding: false,
|
onboarding: false,
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
await utils.resetBackups(admin.accessToken);
|
await utils.resetBackups(admin.accessToken);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ describe('/admin/maintenance', () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
interval: 500,
|
interval: 500,
|
||||||
timeout: 10_000,
|
timeout: 60_000,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.toBeTruthy();
|
.toBeTruthy();
|
||||||
@@ -190,7 +190,7 @@ describe('/admin/maintenance', () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
interval: 500,
|
interval: 500,
|
||||||
timeout: 10_000,
|
timeout: 60_000,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.toBeFalsy();
|
.toBeFalsy();
|
||||||
|
|||||||
@@ -504,13 +504,14 @@ describe('/albums', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not be able to share album with owner', async () => {
|
it('should deduplicate owner from albumUsers on create', async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.post('/albums')
|
.post('/albums')
|
||||||
.send({ albumName: 'New album', albumUsers: [{ role: AlbumUserRole.Editor, userId: user1.userId }] })
|
.send({ albumName: 'New album', albumUsers: [{ role: AlbumUserRole.Editor, userId: user1.userId }] })
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(201);
|
||||||
expect(body).toEqual(errorDto.badRequest('Cannot share album with owner'));
|
expect(body.albumUsers).toHaveLength(1);
|
||||||
|
expect(body.albumUsers[0]).toMatchObject({ role: AlbumUserRole.Owner, user: { id: user1.userId } });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -729,8 +730,8 @@ describe('/albums', () => {
|
|||||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
.send({ albumUsers: [{ userId: user1.userId, role: AlbumUserRole.Editor }] });
|
.send({ albumUsers: [{ userId: user1.userId, role: AlbumUserRole.Editor }] });
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(200);
|
||||||
expect(body).toEqual(errorDto.badRequest('User already added'));
|
expect(body.albumUsers.length).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not be able to add existing user to shared album', async () => {
|
it('should not be able to add existing user to shared album', async () => {
|
||||||
@@ -744,8 +745,8 @@ describe('/albums', () => {
|
|||||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
.send({ albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Editor }] });
|
.send({ albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Editor }] });
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(200);
|
||||||
expect(body).toEqual(errorDto.badRequest('User already added'));
|
expect(body.albumUsers.length).toEqual(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
getMyUser,
|
getMyUser,
|
||||||
LoginResponseDto,
|
LoginResponseDto,
|
||||||
SharedLinkType,
|
SharedLinkType,
|
||||||
updateConfig,
|
|
||||||
} from '@immich/sdk';
|
} from '@immich/sdk';
|
||||||
import { exiftool } from 'exiftool-vendored';
|
import { exiftool } from 'exiftool-vendored';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
@@ -24,7 +23,6 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|||||||
|
|
||||||
const locationAssetFilepath = `${testAssetDir}/metadata/gps-position/thompson-springs.jpg`;
|
const locationAssetFilepath = `${testAssetDir}/metadata/gps-position/thompson-springs.jpg`;
|
||||||
const ratingAssetFilepath = `${testAssetDir}/metadata/rating/mongolels.jpg`;
|
const ratingAssetFilepath = `${testAssetDir}/metadata/rating/mongolels.jpg`;
|
||||||
const facesAssetDir = `${testAssetDir}/metadata/faces`;
|
|
||||||
|
|
||||||
const readTags = async (bytes: Buffer, filename: string) => {
|
const readTags = async (bytes: Buffer, filename: string) => {
|
||||||
const filepath = join(tempDir, filename);
|
const filepath = join(tempDir, filename);
|
||||||
@@ -185,78 +183,6 @@ describe('/asset', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('faces', () => {
|
|
||||||
const metadataFaceTests = [
|
|
||||||
{
|
|
||||||
description: 'without orientation',
|
|
||||||
filename: 'portrait.jpg',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: 'adjusting face regions to orientation',
|
|
||||||
filename: 'portrait-orientation-6.jpg',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
// should produce same resulting face region coordinates for any orientation
|
|
||||||
const expectedFaces = [
|
|
||||||
{
|
|
||||||
name: 'Marie Curie',
|
|
||||||
birthDate: null,
|
|
||||||
isHidden: false,
|
|
||||||
faces: [
|
|
||||||
{
|
|
||||||
imageHeight: 700,
|
|
||||||
imageWidth: 840,
|
|
||||||
boundingBoxX1: 261,
|
|
||||||
boundingBoxX2: 356,
|
|
||||||
boundingBoxY1: 146,
|
|
||||||
boundingBoxY2: 284,
|
|
||||||
sourceType: 'exif',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Pierre Curie',
|
|
||||||
birthDate: null,
|
|
||||||
isHidden: false,
|
|
||||||
faces: [
|
|
||||||
{
|
|
||||||
imageHeight: 700,
|
|
||||||
imageWidth: 840,
|
|
||||||
boundingBoxX1: 536,
|
|
||||||
boundingBoxX2: 618,
|
|
||||||
boundingBoxY1: 83,
|
|
||||||
boundingBoxY2: 252,
|
|
||||||
sourceType: 'exif',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
it.each(metadataFaceTests)('should get the asset faces from $filename $description', async ({ filename }) => {
|
|
||||||
const config = await utils.getSystemConfig(admin.accessToken);
|
|
||||||
config.metadata.faces.import = true;
|
|
||||||
await updateConfig({ systemConfigDto: config }, { headers: asBearerAuth(admin.accessToken) });
|
|
||||||
|
|
||||||
const facesAsset = await utils.createAsset(admin.accessToken, {
|
|
||||||
assetData: {
|
|
||||||
filename,
|
|
||||||
bytes: await readFile(`${facesAssetDir}/${filename}`),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: facesAsset.id });
|
|
||||||
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.get(`/assets/${facesAsset.id}`)
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
|
||||||
|
|
||||||
expect(status).toBe(200);
|
|
||||||
expect(body.id).toEqual(facesAsset.id);
|
|
||||||
const sortedPeople = body.people.toSorted((a: any, b: any) => a.name.localeCompare(b.name));
|
|
||||||
expect(sortedPeople).toMatchObject(expectedFaces);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should work with a shared link', async () => {
|
it('should work with a shared link', async () => {
|
||||||
const sharedLink = await utils.createSharedLink(user1.accessToken, {
|
const sharedLink = await utils.createSharedLink(user1.accessToken, {
|
||||||
type: SharedLinkType.Individual,
|
type: SharedLinkType.Individual,
|
||||||
|
|||||||
@@ -0,0 +1,669 @@
|
|||||||
|
import {
|
||||||
|
AssetMediaResponseDto,
|
||||||
|
IntegrityReportResponseDto,
|
||||||
|
LoginResponseDto,
|
||||||
|
ManualJobName,
|
||||||
|
QueueCommand,
|
||||||
|
QueueName,
|
||||||
|
} from '@immich/sdk';
|
||||||
|
import { readFile } from 'node:fs/promises';
|
||||||
|
import { app, testAssetDir, utils } from 'src/utils';
|
||||||
|
import request from 'supertest';
|
||||||
|
import { afterEach, beforeAll, describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
const assetFilepath = `${testAssetDir}/metadata/gps-position/thompson-springs.jpg`;
|
||||||
|
const asset1Filepath = `${testAssetDir}/albums/nature/el_torcal_rocks.jpg`;
|
||||||
|
const asset2Filepath = `${testAssetDir}/albums/nature/wood_anemones.jpg`;
|
||||||
|
|
||||||
|
describe('/admin/integrity', () => {
|
||||||
|
let admin: LoginResponseDto;
|
||||||
|
let asset: AssetMediaResponseDto;
|
||||||
|
|
||||||
|
let user1: LoginResponseDto;
|
||||||
|
let asset1: AssetMediaResponseDto;
|
||||||
|
|
||||||
|
let user2: LoginResponseDto;
|
||||||
|
let asset2: AssetMediaResponseDto;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await utils.resetDatabase();
|
||||||
|
admin = await utils.adminSetup();
|
||||||
|
|
||||||
|
user1 = await utils.userSetup(admin.accessToken, {
|
||||||
|
email: '1@example.com',
|
||||||
|
name: '1',
|
||||||
|
password: '1',
|
||||||
|
});
|
||||||
|
|
||||||
|
user2 = await utils.userSetup(admin.accessToken, {
|
||||||
|
email: '2@example.com',
|
||||||
|
name: '2',
|
||||||
|
password: '2',
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const queue of Object.values(QueueName)) {
|
||||||
|
if (queue === QueueName.IntegrityCheck) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await utils.queueCommand(admin.accessToken, queue, {
|
||||||
|
command: QueueCommand.Pause,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
asset = await utils.createAsset(admin.accessToken, {
|
||||||
|
assetData: {
|
||||||
|
filename: 'asset.jpg',
|
||||||
|
bytes: await readFile(assetFilepath),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
asset1 = await utils.createAsset(user1.accessToken, {
|
||||||
|
assetData: {
|
||||||
|
filename: 'asset.jpg',
|
||||||
|
bytes: await readFile(asset1Filepath),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
asset2 = await utils.createAsset(user2.accessToken, {
|
||||||
|
assetData: {
|
||||||
|
filename: 'asset.jpg',
|
||||||
|
bytes: await readFile(asset2Filepath),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.mkFolder('/data/bak');
|
||||||
|
await utils.copyFolder(`/data/upload/${admin.userId}`, `/data/bak/${admin.userId}`);
|
||||||
|
|
||||||
|
for (const queue of Object.values(QueueName)) {
|
||||||
|
if (queue === QueueName.IntegrityCheck) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await utils.queueCommand(admin.accessToken, queue, {
|
||||||
|
command: QueueCommand.Empty,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.queueCommand(admin.accessToken, queue, {
|
||||||
|
command: QueueCommand.Resume,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await utils.deleteFolder(`/data/upload/${admin.userId}`);
|
||||||
|
await utils.copyFolder(`/data/bak/${admin.userId}`, `/data/upload/${admin.userId}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('POST /summary (& jobs)', async () => {
|
||||||
|
it.sequential('reports no issues', async () => {
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityUntrackedFiles,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityMissingFiles,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityChecksumMismatch,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityUntrackedFilesDeleteAll,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/admin/integrity/summary')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual({
|
||||||
|
missing_file: 0,
|
||||||
|
untracked_file: 0,
|
||||||
|
checksum_mismatch: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it.sequential('should detect an untracked file (job: check untracked files)', async () => {
|
||||||
|
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`);
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityUntrackedFiles,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/admin/integrity/summary')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
untracked_file: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.sequential('should detect outdated untracked file reports (job: refresh untracked files)', async () => {
|
||||||
|
// these should not be detected:
|
||||||
|
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked2.png`);
|
||||||
|
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked3.png`);
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityUntrackedFilesRefresh,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/admin/integrity/summary')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
untracked_file: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.sequential('should delete untracked files (job: delete all untracked file reports)', async () => {
|
||||||
|
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`);
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityUntrackedFiles,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityUntrackedFilesDeleteAll,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/admin/integrity/summary')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
untracked_file: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.sequential('should detect a missing file and not a checksum mismatch (job: check missing files)', async () => {
|
||||||
|
await utils.deleteFolder(`/data/upload/${admin.userId}`);
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityMissingFiles,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/admin/integrity/summary')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
missing_file: 1,
|
||||||
|
checksum_mismatch: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.sequential('should detect outdated missing file reports (job: refresh missing files)', async () => {
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityMissingFilesRefresh,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/admin/integrity/summary')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
missing_file: 0,
|
||||||
|
checksum_mismatch: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.sequential('should delete assets with missing files (job: delete all missing file reports)', async () => {
|
||||||
|
await utils.deleteFolder(`/data/upload/${user1.userId}`);
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityMissingFiles,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { status: listStatus, body: listBody } = await request(app)
|
||||||
|
.get('/admin/integrity/summary')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(listStatus).toBe(200);
|
||||||
|
expect(listBody).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
missing_file: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityMissingFilesDeleteAll,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/admin/integrity/summary')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
missing_file: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(utils.getAssetInfo(user1.accessToken, asset1.id)).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
isTrashed: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.sequential('should detect a checksum mismatch (job: check file checksums)', async () => {
|
||||||
|
await utils.truncateFolder(`/data/upload/${admin.userId}`);
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityChecksumMismatch,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/admin/integrity/summary')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
checksum_mismatch: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.sequential('should detect outdated checksum mismatch reports (job: refresh file checksums)', async () => {
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityChecksumMismatchRefresh,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/admin/integrity/summary')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
checksum_mismatch: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.sequential(
|
||||||
|
'should delete assets with mismatched checksum (job: delete all checksum mismatch reports)',
|
||||||
|
async () => {
|
||||||
|
await utils.truncateFolder(`/data/upload/${user2.userId}`);
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityChecksumMismatch,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { status: listStatus, body: listBody } = await request(app)
|
||||||
|
.get('/admin/integrity/summary')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(listStatus).toBe(200);
|
||||||
|
expect(listBody).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
checksum_mismatch: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityChecksumMismatchDeleteAll,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/admin/integrity/summary')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
checksum_mismatch: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(utils.getAssetInfo(user2.accessToken, asset2.id)).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
isTrashed: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('POST /report', async () => {
|
||||||
|
it.sequential('reports untracked files', async () => {
|
||||||
|
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`);
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityUntrackedFiles,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/admin/integrity/report?type=untracked_file')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual({
|
||||||
|
nextCursor: undefined,
|
||||||
|
items: expect.arrayContaining([
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
type: 'untracked_file',
|
||||||
|
path: `/data/upload/${admin.userId}/untracked1.png`,
|
||||||
|
assetId: null,
|
||||||
|
fileAssetId: null,
|
||||||
|
createdAt: expect.any(String),
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it.sequential('reports missing files', async () => {
|
||||||
|
await utils.deleteFolder(`/data/upload/${admin.userId}`);
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityMissingFiles,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/admin/integrity/report?type=missing_file')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual({
|
||||||
|
nextCursor: undefined,
|
||||||
|
items: expect.arrayContaining([
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
type: 'missing_file',
|
||||||
|
path: expect.any(String),
|
||||||
|
assetId: asset.id,
|
||||||
|
fileAssetId: null,
|
||||||
|
createdAt: expect.any(String),
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it.sequential('reports checksum mismatched files', async () => {
|
||||||
|
await utils.truncateFolder(`/data/upload/${admin.userId}`);
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityChecksumMismatch,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/admin/integrity/report?type=checksum_mismatch')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual({
|
||||||
|
nextCursor: undefined,
|
||||||
|
items: expect.arrayContaining([
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
type: 'checksum_mismatch',
|
||||||
|
path: expect.any(String),
|
||||||
|
assetId: asset.id,
|
||||||
|
fileAssetId: null,
|
||||||
|
createdAt: expect.any(String),
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DELETE /report/:id', async () => {
|
||||||
|
it.sequential('delete untracked files', async () => {
|
||||||
|
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`);
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityUntrackedFiles,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { status: listStatus, body: listBody } = await request(app)
|
||||||
|
.get('/admin/integrity/report?type=untracked_file')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(listStatus).toBe(200);
|
||||||
|
|
||||||
|
const report = (listBody as IntegrityReportResponseDto).items.find(
|
||||||
|
(item) => item.path === `/data/upload/${admin.userId}/untracked1.png`,
|
||||||
|
)!;
|
||||||
|
|
||||||
|
const { status } = await request(app)
|
||||||
|
.delete(`/admin/integrity/report/${report.id}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityUntrackedFiles,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { status: listStatus2, body: listBody2 } = await request(app)
|
||||||
|
.get('/admin/integrity/report?type=untracked_file')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(listStatus2).toBe(200);
|
||||||
|
expect(listBody2).not.toBe(
|
||||||
|
expect.objectContaining({
|
||||||
|
items: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: report.id,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.sequential('delete assets missing files', async () => {
|
||||||
|
await utils.deleteFolder(`/data/upload/${admin.userId}`);
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityMissingFiles,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { status: listStatus, body: listBody } = await request(app)
|
||||||
|
.get('/admin/integrity/report?type=missing_file')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(listStatus).toBe(200);
|
||||||
|
expect(listBody.items.length).toBe(1);
|
||||||
|
|
||||||
|
const report = (listBody as IntegrityReportResponseDto).items[0];
|
||||||
|
|
||||||
|
const { status } = await request(app)
|
||||||
|
.delete(`/admin/integrity/report/${report.id}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityMissingFiles,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { status: listStatus2, body: listBody2 } = await request(app)
|
||||||
|
.get('/admin/integrity/report?type=missing_file')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(listStatus2).toBe(200);
|
||||||
|
expect(listBody2.items.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.sequential('delete assets with failing checksum', async () => {
|
||||||
|
await utils.truncateFolder(`/data/upload/${admin.userId}`);
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityChecksumMismatch,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { status: listStatus, body: listBody } = await request(app)
|
||||||
|
.get('/admin/integrity/report?type=checksum_mismatch')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(listStatus).toBe(200);
|
||||||
|
expect(listBody.items.length).toBe(1);
|
||||||
|
|
||||||
|
const report = (listBody as IntegrityReportResponseDto).items[0];
|
||||||
|
|
||||||
|
const { status } = await request(app)
|
||||||
|
.delete(`/admin/integrity/report/${report.id}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityChecksumMismatch,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { status: listStatus2, body: listBody2 } = await request(app)
|
||||||
|
.get('/admin/integrity/report?type=checksum_mismatch')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(listStatus2).toBe(200);
|
||||||
|
expect(listBody2.items.length).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /report/:type/csv', () => {
|
||||||
|
it.sequential('exports untracked files as csv', async () => {
|
||||||
|
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`);
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityUntrackedFiles,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { status, headers, text } = await request(app)
|
||||||
|
.get('/admin/integrity/report/untracked_file/csv')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(headers['content-type']).toContain('text/csv');
|
||||||
|
expect(headers['content-disposition']).toContain('.csv');
|
||||||
|
expect(text).toContain('id,type,assetId,fileAssetId,path');
|
||||||
|
expect(text).toContain(`untracked_file`);
|
||||||
|
expect(text).toContain(`/data/upload/${admin.userId}/untracked1.png`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /report/:id/file', () => {
|
||||||
|
it.sequential('downloads untracked file', async () => {
|
||||||
|
await utils.putTextFile('untracked-content', `/data/upload/${admin.userId}/untracked1.png`);
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityUntrackedFiles,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { body: listBody } = await request(app)
|
||||||
|
.get('/admin/integrity/report?type=untracked_file')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
const report = (listBody as IntegrityReportResponseDto).items.find(
|
||||||
|
(item) => item.path === `/data/upload/${admin.userId}/untracked1.png`,
|
||||||
|
)!;
|
||||||
|
|
||||||
|
const { status, headers, body } = await request(app)
|
||||||
|
.get(`/admin/integrity/report/${report.id}/file`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.buffer(true)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(headers['content-type']).toContain('application/octet-stream');
|
||||||
|
expect(body.toString()).toBe('untracked-content');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -2,6 +2,7 @@ import { AssetVisibility, LoginResponseDto } from '@immich/sdk';
|
|||||||
import { readFile } from 'node:fs/promises';
|
import { readFile } from 'node:fs/promises';
|
||||||
import { basename, join } from 'node:path';
|
import { basename, join } from 'node:path';
|
||||||
import { Socket } from 'socket.io-client';
|
import { Socket } from 'socket.io-client';
|
||||||
|
import { createUserDto } from 'src/fixtures';
|
||||||
import { errorDto } from 'src/responses';
|
import { errorDto } from 'src/responses';
|
||||||
import { app, testAssetDir, utils } from 'src/utils';
|
import { app, testAssetDir, utils } from 'src/utils';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
@@ -9,28 +10,48 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|||||||
|
|
||||||
describe('/map', () => {
|
describe('/map', () => {
|
||||||
let websocket: Socket;
|
let websocket: Socket;
|
||||||
|
let partnerWebsocket: Socket;
|
||||||
let admin: LoginResponseDto;
|
let admin: LoginResponseDto;
|
||||||
|
let partner: LoginResponseDto;
|
||||||
|
let partnerArchivedAssetId: string;
|
||||||
|
let adminArchivedAssetId: string;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await utils.resetDatabase();
|
await utils.resetDatabase();
|
||||||
admin = await utils.adminSetup({ onboarding: false });
|
admin = await utils.adminSetup({ onboarding: false });
|
||||||
|
partner = await utils.userSetup(admin.accessToken, createUserDto.user1);
|
||||||
|
|
||||||
websocket = await utils.connectWebsocket(admin.accessToken);
|
websocket = await utils.connectWebsocket(admin.accessToken);
|
||||||
|
partnerWebsocket = await utils.connectWebsocket(partner.accessToken);
|
||||||
|
|
||||||
const files = ['formats/heic/IMG_2682.heic', 'metadata/gps-position/thompson-springs.jpg'];
|
const adminFiles = ['formats/heic/IMG_2682.heic', 'metadata/gps-position/thompson-springs.jpg'];
|
||||||
|
const adminArchivedFile = 'metadata/dates/datetimeoriginal-gps.jpg';
|
||||||
|
const partnerFile = 'metadata/gps-position/thompson-springs.jpg';
|
||||||
utils.resetEvents();
|
utils.resetEvents();
|
||||||
const uploadFile = async (input: string) => {
|
const uploadFile = async (accessToken: string, input: string) => {
|
||||||
const filepath = join(testAssetDir, input);
|
const filepath = join(testAssetDir, input);
|
||||||
const { id } = await utils.createAsset(admin.accessToken, {
|
const { id } = await utils.createAsset(accessToken, {
|
||||||
assetData: { bytes: await readFile(filepath), filename: basename(filepath) },
|
assetData: { bytes: await readFile(filepath), filename: basename(filepath) },
|
||||||
});
|
});
|
||||||
await utils.waitForWebsocketEvent({ event: 'assetUpload', id });
|
await utils.waitForWebsocketEvent({ event: 'assetUpload', id });
|
||||||
|
return id;
|
||||||
};
|
};
|
||||||
await Promise.all(files.map((f) => uploadFile(f)));
|
await Promise.all(adminFiles.map((f) => uploadFile(admin.accessToken, f)));
|
||||||
|
[adminArchivedAssetId, partnerArchivedAssetId] = await Promise.all([
|
||||||
|
uploadFile(admin.accessToken, adminArchivedFile),
|
||||||
|
uploadFile(partner.accessToken, partnerFile),
|
||||||
|
]);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
utils.archiveAssets(admin.accessToken, [adminArchivedAssetId]),
|
||||||
|
utils.archiveAssets(partner.accessToken, [partnerArchivedAssetId]),
|
||||||
|
utils.createPartner(partner.accessToken, admin.userId),
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
utils.disconnectWebsocket(websocket);
|
utils.disconnectWebsocket(websocket);
|
||||||
|
utils.disconnectWebsocket(partnerWebsocket);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('GET /map/markers', () => {
|
describe('GET /map/markers', () => {
|
||||||
@@ -40,7 +61,6 @@ describe('/map', () => {
|
|||||||
expect(body).toEqual(errorDto.unauthorized);
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO archive one of these assets
|
|
||||||
it('should get map markers for all non-archived assets', async () => {
|
it('should get map markers for all non-archived assets', async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.get('/map/markers')
|
.get('/map/markers')
|
||||||
@@ -69,7 +89,28 @@ describe('/map', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO archive one of these assets
|
it('should not expose partner archived asset locations', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/map/markers')
|
||||||
|
.query({ withPartners: true, isArchived: true })
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
const ids = body.map((m: { id: string }) => m.id);
|
||||||
|
expect(ids).not.toContain(partnerArchivedAssetId);
|
||||||
|
expect(ids).toContain(adminArchivedAssetId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include own archived asset locations', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/map/markers')
|
||||||
|
.query({ isArchived: true })
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body.map((m: { id: string }) => m.id)).toContain(adminArchivedAssetId);
|
||||||
|
});
|
||||||
|
|
||||||
it('should get all map markers', async () => {
|
it('should get all map markers', async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.get('/map/markers')
|
.get('/map/markers')
|
||||||
|
|||||||
@@ -259,17 +259,6 @@ describe('/search', () => {
|
|||||||
assets: [assetHeic],
|
assets: [assetHeic],
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
should: "should search city ('')",
|
|
||||||
deferred: () => ({
|
|
||||||
dto: {
|
|
||||||
city: '',
|
|
||||||
visibility: AssetVisibility.Timeline,
|
|
||||||
includeNull: true,
|
|
||||||
},
|
|
||||||
assets: [assetLast],
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
should: 'should search city (null)',
|
should: 'should search city (null)',
|
||||||
deferred: () => ({
|
deferred: () => ({
|
||||||
@@ -291,18 +280,6 @@ describe('/search', () => {
|
|||||||
assets: [assetDensity],
|
assets: [assetDensity],
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
should: "should search state ('')",
|
|
||||||
deferred: () => ({
|
|
||||||
dto: {
|
|
||||||
state: '',
|
|
||||||
visibility: AssetVisibility.Timeline,
|
|
||||||
withExif: true,
|
|
||||||
includeNull: true,
|
|
||||||
},
|
|
||||||
assets: [assetLast, assetNotocactus],
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
should: 'should search state (null)',
|
should: 'should search state (null)',
|
||||||
deferred: () => ({
|
deferred: () => ({
|
||||||
@@ -324,17 +301,6 @@ describe('/search', () => {
|
|||||||
assets: [assetFalcon],
|
assets: [assetFalcon],
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
should: "should search country ('')",
|
|
||||||
deferred: () => ({
|
|
||||||
dto: {
|
|
||||||
country: '',
|
|
||||||
visibility: AssetVisibility.Timeline,
|
|
||||||
includeNull: true,
|
|
||||||
},
|
|
||||||
assets: [assetLast],
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
should: 'should search country (null)',
|
should: 'should search country (null)',
|
||||||
deferred: () => ({
|
deferred: () => ({
|
||||||
@@ -441,7 +407,18 @@ describe('/search', () => {
|
|||||||
.get('/search/explore')
|
.get('/search/explore')
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toEqual([{ fieldName: 'exifInfo.city', items: [] }]);
|
expect(Array.isArray(body)).toBe(true);
|
||||||
|
expect(body).toEqual(expect.arrayContaining([{ fieldName: 'exifInfo.city', items: [] }]));
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
{
|
||||||
|
fieldName: 'createdAt',
|
||||||
|
items: expect.arrayContaining([
|
||||||
|
expect.objectContaining({ data: expect.objectContaining({ id: assetLast.id }) }),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ describe('/server', () => {
|
|||||||
major: expect.any(Number),
|
major: expect.any(Number),
|
||||||
minor: expect.any(Number),
|
minor: expect.any(Number),
|
||||||
patch: expect.any(Number),
|
patch: expect.any(Number),
|
||||||
|
prerelease: expect.anything(),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -115,6 +116,7 @@ describe('/server', () => {
|
|||||||
oauthAutoLaunch: false,
|
oauthAutoLaunch: false,
|
||||||
ocr: false,
|
ocr: false,
|
||||||
passwordLogin: true,
|
passwordLogin: true,
|
||||||
|
realtimeTranscoding: false,
|
||||||
search: true,
|
search: true,
|
||||||
sidecar: true,
|
sidecar: true,
|
||||||
trash: true,
|
trash: true,
|
||||||
@@ -139,6 +141,7 @@ describe('/server', () => {
|
|||||||
maintenanceMode: false,
|
maintenanceMode: false,
|
||||||
mapDarkStyleUrl: 'https://tiles.immich.cloud/v1/style/dark.json',
|
mapDarkStyleUrl: 'https://tiles.immich.cloud/v1/style/dark.json',
|
||||||
mapLightStyleUrl: 'https://tiles.immich.cloud/v1/style/light.json',
|
mapLightStyleUrl: 'https://tiles.immich.cloud/v1/style/light.json',
|
||||||
|
minFaces: 3,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,18 +21,18 @@ describe('/system-config', () => {
|
|||||||
const response1 = await request(app)
|
const response1 = await request(app)
|
||||||
.put('/system-config')
|
.put('/system-config')
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
.send({ ...config, newVersionCheck: { enabled: false } });
|
.send({ ...config, newVersionCheck: { enabled: false, channel: 'stable' } });
|
||||||
|
|
||||||
expect(response1.status).toBe(200);
|
expect(response1.status).toBe(200);
|
||||||
expect(response1.body).toEqual({ ...config, newVersionCheck: { enabled: false } });
|
expect(response1.body).toEqual({ ...config, newVersionCheck: { enabled: false, channel: 'stable' } });
|
||||||
|
|
||||||
const response2 = await request(app)
|
const response2 = await request(app)
|
||||||
.put('/system-config')
|
.put('/system-config')
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
.send({ ...config, newVersionCheck: { enabled: true } });
|
.send({ ...config, newVersionCheck: { enabled: true, channel: 'stable' } });
|
||||||
|
|
||||||
expect(response2.status).toBe(200);
|
expect(response2.status).toBe(200);
|
||||||
expect(response2.body).toEqual({ ...config, newVersionCheck: { enabled: true } });
|
expect(response2.body).toEqual({ ...config, newVersionCheck: { enabled: true, channel: 'stable' } });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reject an invalid config entry', async () => {
|
it('should reject an invalid config entry', async () => {
|
||||||
|
|||||||
@@ -230,6 +230,21 @@ describe('/users', () => {
|
|||||||
const after = await getMyPreferences({ headers: asBearerAuth(admin.accessToken) });
|
const after = await getMyPreferences({ headers: asBearerAuth(admin.accessToken) });
|
||||||
expect(after).toMatchObject({ download: { includeEmbeddedVideos: true } });
|
expect(after).toMatchObject({ download: { includeEmbeddedVideos: true } });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should update minimum face count to display people', async () => {
|
||||||
|
const before = await getMyPreferences({ headers: asBearerAuth(admin.accessToken) });
|
||||||
|
expect(before).toMatchObject({ people: { minimumFaces: 3 } });
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put('/users/me/preferences')
|
||||||
|
.send({ people: { minimumFaces: 2 } })
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toMatchObject({ people: { minimumFaces: 2 } });
|
||||||
|
|
||||||
|
const after = await getMyPreferences({ headers: asBearerAuth(admin.accessToken) });
|
||||||
|
expect(after).toMatchObject({ people: { minimumFaces: 2 } });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('GET /users/:id', () => {
|
describe('GET /users/:id', () => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { readFileSync } from 'node:fs';
|
|||||||
import { immichCli } from 'src/utils';
|
import { immichCli } from 'src/utils';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
const pkg = JSON.parse(readFileSync('../cli/package.json', 'utf8'));
|
const pkg = JSON.parse(readFileSync('../packages/cli/package.json', 'utf8'));
|
||||||
|
|
||||||
describe(`immich --version`, () => {
|
describe(`immich --version`, () => {
|
||||||
describe('immich --version', () => {
|
describe('immich --version', () => {
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { LoginResponseDto, ManualJobName, QueueName } from '@immich/sdk';
|
||||||
|
import { expect, test } from '@playwright/test';
|
||||||
|
import { utils } from 'src/utils';
|
||||||
|
|
||||||
|
test.describe.configure({ mode: 'serial' });
|
||||||
|
|
||||||
|
test.describe.skip('Integrity', () => {
|
||||||
|
let admin: LoginResponseDto;
|
||||||
|
|
||||||
|
test.beforeAll(async () => {
|
||||||
|
utils.initSdk();
|
||||||
|
await utils.resetDatabase();
|
||||||
|
admin = await utils.adminSetup();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('run integrity jobs to update stats', async ({ context, page }) => {
|
||||||
|
await utils.setAuthCookies(context, admin.accessToken);
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityUntrackedFiles,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
await page.goto('/admin/maintenance');
|
||||||
|
|
||||||
|
const count = page.getByText('Untracked Files').locator('..').locator('..').locator('div').nth(1);
|
||||||
|
|
||||||
|
const previousCount = Number.parseInt((await count.textContent()) ?? '');
|
||||||
|
|
||||||
|
await utils.mkFolder(`/data/upload/${admin.userId}`);
|
||||||
|
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`);
|
||||||
|
|
||||||
|
const checkButton = page.getByText('Integrity Report').locator('..').getByRole('button', { name: 'Check All' });
|
||||||
|
|
||||||
|
await checkButton.click();
|
||||||
|
await expect(checkButton).toBeEnabled();
|
||||||
|
|
||||||
|
await expect(count).toContainText((previousCount + 1).toString());
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -28,6 +28,7 @@ export function toColumnarFormat(assets: MockTimelineAsset[]): TimeBucketAssetRe
|
|||||||
ownerId: [],
|
ownerId: [],
|
||||||
ratio: [],
|
ratio: [],
|
||||||
thumbhash: [],
|
thumbhash: [],
|
||||||
|
createdAt: [],
|
||||||
fileCreatedAt: [],
|
fileCreatedAt: [],
|
||||||
localOffsetHours: [],
|
localOffsetHours: [],
|
||||||
isFavorite: [],
|
isFavorite: [],
|
||||||
@@ -54,8 +55,8 @@ export function toColumnarFormat(assets: MockTimelineAsset[]): TimeBucketAssetRe
|
|||||||
result.duration.push(asset.duration);
|
result.duration.push(asset.duration);
|
||||||
result.projectionType.push(asset.projectionType);
|
result.projectionType.push(asset.projectionType);
|
||||||
result.livePhotoVideoId.push(asset.livePhotoVideoId);
|
result.livePhotoVideoId.push(asset.livePhotoVideoId);
|
||||||
result.city.push(asset.city);
|
result.city?.push(asset.city);
|
||||||
result.country.push(asset.country);
|
result.country?.push(asset.country);
|
||||||
result.visibility.push(asset.visibility);
|
result.visibility.push(asset.visibility);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,7 +339,6 @@ export function toAssetResponseDto(asset: MockTimelineAsset, owner?: UserRespons
|
|||||||
livePhotoVideoId: asset.livePhotoVideoId,
|
livePhotoVideoId: asset.livePhotoVideoId,
|
||||||
tags: [],
|
tags: [],
|
||||||
people: [],
|
people: [],
|
||||||
unassignedFaces: [],
|
|
||||||
stack: asset.stack,
|
stack: asset.stack,
|
||||||
isOffline: false,
|
isOffline: false,
|
||||||
hasMetadata: true,
|
hasMetadata: true,
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ export const createMockStackAsset = (ownerId: string): AssetResponseDto => {
|
|||||||
livePhotoVideoId: null,
|
livePhotoVideoId: null,
|
||||||
tags: [],
|
tags: [],
|
||||||
people: [],
|
people: [],
|
||||||
unassignedFaces: [],
|
|
||||||
stack: undefined,
|
stack: undefined,
|
||||||
isOffline: false,
|
isOffline: false,
|
||||||
hasMetadata: true,
|
hasMetadata: true,
|
||||||
|
|||||||
@@ -536,7 +536,7 @@ test.describe('Timeline', () => {
|
|||||||
force: false,
|
force: false,
|
||||||
ids: [assetToTrash.id],
|
ids: [assetToTrash.id],
|
||||||
});
|
});
|
||||||
await page.locator('#asset-selection-app-bar').getByLabel('Close').click();
|
await page.locator('#control-bar').getByLabel('Close').click();
|
||||||
await page.getByText('Trash', { exact: true }).click();
|
await page.getByText('Trash', { exact: true }).click();
|
||||||
await timelineUtils.waitForTimelineLoad(page);
|
await timelineUtils.waitForTimelineLoad(page);
|
||||||
await thumbnailUtils.expectInViewport(page, assetToTrash.id);
|
await thumbnailUtils.expectInViewport(page, assetToTrash.id);
|
||||||
@@ -676,7 +676,7 @@ test.describe('Timeline', () => {
|
|||||||
ids: [assetToArchive.id],
|
ids: [assetToArchive.id],
|
||||||
});
|
});
|
||||||
await thumbnailUtils.expectThumbnailIsArchive(page, assetToArchive.id);
|
await thumbnailUtils.expectThumbnailIsArchive(page, assetToArchive.id);
|
||||||
await page.locator('#asset-selection-app-bar').getByLabel('Close').click();
|
await page.locator('#control-bar').getByLabel('Close').click();
|
||||||
await page.getByRole('link').getByText('Archive').click();
|
await page.getByRole('link').getByText('Archive').click();
|
||||||
await timelineUtils.waitForTimelineLoad(page);
|
await timelineUtils.waitForTimelineLoad(page);
|
||||||
await thumbnailUtils.expectInViewport(page, assetToArchive.id);
|
await thumbnailUtils.expectInViewport(page, assetToArchive.id);
|
||||||
@@ -823,7 +823,7 @@ test.describe('Timeline', () => {
|
|||||||
});
|
});
|
||||||
// ensure thumbnail still exists and has favorite icon
|
// ensure thumbnail still exists and has favorite icon
|
||||||
await thumbnailUtils.expectThumbnailIsFavorite(page, assetToFavorite.id);
|
await thumbnailUtils.expectThumbnailIsFavorite(page, assetToFavorite.id);
|
||||||
await page.locator('#asset-selection-app-bar').getByLabel('Close').click();
|
await page.locator('#control-bar').getByLabel('Close').click();
|
||||||
await page.getByRole('link').getByText('Favorites').click();
|
await page.getByRole('link').getByText('Favorites').click();
|
||||||
await timelineUtils.waitForTimelineLoad(page);
|
await timelineUtils.waitForTimelineLoad(page);
|
||||||
await pageUtils.goToAsset(page, assetToFavorite.fileCreatedAt);
|
await pageUtils.goToAsset(page, assetToFavorite.fileCreatedAt);
|
||||||
|
|||||||
+49
-4
@@ -90,7 +90,7 @@ export const tempDir = tmpdir();
|
|||||||
export const asBearerAuth = (accessToken: string) => ({ Authorization: `Bearer ${accessToken}` });
|
export const asBearerAuth = (accessToken: string) => ({ Authorization: `Bearer ${accessToken}` });
|
||||||
export const asKeyAuth = (key: string) => ({ 'x-api-key': key });
|
export const asKeyAuth = (key: string) => ({ 'x-api-key': key });
|
||||||
export const immichCli = (args: string[]) =>
|
export const immichCli = (args: string[]) =>
|
||||||
executeCommand('pnpm', ['exec', 'immich', '-d', `/${tempDir}/immich/`, ...args], { cwd: '../cli' }).promise;
|
executeCommand('pnpm', ['exec', 'immich', '-d', `/${tempDir}/immich/`, ...args], { cwd: '../packages/cli' }).promise;
|
||||||
export const dockerExec = (args: string[]) =>
|
export const dockerExec = (args: string[]) =>
|
||||||
executeCommand('docker', ['exec', '-i', 'immich-e2e-server', '/bin/bash', '-c', args.join(' ')]);
|
executeCommand('docker', ['exec', '-i', 'immich-e2e-server', '/bin/bash', '-c', args.join(' ')]);
|
||||||
export const immichAdmin = (args: string[]) => dockerExec([`immich-admin ${args.join(' ')}`]);
|
export const immichAdmin = (args: string[]) => dockerExec([`immich-admin ${args.join(' ')}`]);
|
||||||
@@ -192,6 +192,7 @@ export const utils = {
|
|||||||
'user',
|
'user',
|
||||||
'system_metadata',
|
'system_metadata',
|
||||||
'tag',
|
'tag',
|
||||||
|
'integrity_report',
|
||||||
];
|
];
|
||||||
|
|
||||||
const truncateTables = tables.filter((table) => table !== 'system_metadata');
|
const truncateTables = tables.filter((table) => table !== 'system_metadata');
|
||||||
@@ -559,15 +560,61 @@ export const utils = {
|
|||||||
mkdirSync(`${testAssetDir}/temp`, { recursive: true });
|
mkdirSync(`${testAssetDir}/temp`, { recursive: true });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
putFile(source: string, dest: string) {
|
||||||
|
return executeCommand('docker', ['cp', source, `immich-e2e-server:${dest}`]).promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
async putTextFile(contents: string, dest: string) {
|
||||||
|
const dir = await mkdtemp(join(tmpdir(), 'test-'));
|
||||||
|
const fn = join(dir, 'file');
|
||||||
|
await pipeline(Readable.from(contents), createWriteStream(fn));
|
||||||
|
return executeCommand('docker', ['cp', fn, `immich-e2e-server:${dest}`]).promise;
|
||||||
|
},
|
||||||
|
|
||||||
async move(source: string, dest: string) {
|
async move(source: string, dest: string) {
|
||||||
return executeCommand('docker', ['exec', 'immich-e2e-server', 'mv', source, dest]).promise;
|
return executeCommand('docker', ['exec', 'immich-e2e-server', 'mv', source, dest]).promise;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async copyFolder(source: string, dest: string) {
|
||||||
|
return executeCommand('docker', ['exec', 'immich-e2e-server', 'cp', '-r', source, dest]).promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteFile(path: string) {
|
||||||
|
return executeCommand('docker', ['exec', 'immich-e2e-server', 'rm', path]).promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteFolder(path: string) {
|
||||||
|
return executeCommand('docker', ['exec', 'immich-e2e-server', 'rm', '-r', path]).promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
async truncateFolder(path: string) {
|
||||||
|
return executeCommand('docker', [
|
||||||
|
'exec',
|
||||||
|
'immich-e2e-server',
|
||||||
|
'find',
|
||||||
|
path,
|
||||||
|
'-type',
|
||||||
|
'f',
|
||||||
|
'-exec',
|
||||||
|
'truncate',
|
||||||
|
'-s',
|
||||||
|
'1',
|
||||||
|
'{}',
|
||||||
|
';',
|
||||||
|
]).promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
async mkFolder(path: string) {
|
||||||
|
return executeCommand('docker', ['exec', 'immich-e2e-server', 'mkdir', '-p', path]).promise;
|
||||||
|
},
|
||||||
|
|
||||||
createBackup: async (accessToken: string) => {
|
createBackup: async (accessToken: string) => {
|
||||||
await utils.createJob(accessToken, {
|
await utils.createJob(accessToken, {
|
||||||
name: ManualJobName.BackupDatabase,
|
name: ManualJobName.BackupDatabase,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(accessToken, 'backupDatabase');
|
||||||
|
|
||||||
return utils.poll(
|
return utils.poll(
|
||||||
() => request(app).get('/admin/database-backups').set('Authorization', `Bearer ${accessToken}`),
|
() => request(app).get('/admin/database-backups').set('Authorization', `Bearer ${accessToken}`),
|
||||||
({ status, body }) => status === 200 && body.backups.length === 1,
|
({ status, body }) => status === 200 && body.backups.length === 1,
|
||||||
@@ -577,10 +624,8 @@ export const utils = {
|
|||||||
|
|
||||||
resetBackups: async (accessToken: string) => {
|
resetBackups: async (accessToken: string) => {
|
||||||
const { backups } = await listDatabaseBackups({ headers: asBearerAuth(accessToken) });
|
const { backups } = await listDatabaseBackups({ headers: asBearerAuth(accessToken) });
|
||||||
|
|
||||||
const backupFiles = backups.map((b) => b.filename);
|
|
||||||
await deleteDatabaseBackup(
|
await deleteDatabaseBackup(
|
||||||
{ databaseBackupDeleteDto: { backups: backupFiles } },
|
{ databaseBackupDeleteDto: { backups: backups.map((dto) => dto.filename) } },
|
||||||
{ headers: asBearerAuth(accessToken) },
|
{ headers: asBearerAuth(accessToken) },
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import tsconfigPaths from 'vite-tsconfig-paths';
|
|
||||||
import { defineConfig } from 'vitest/config';
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
const skipDockerSetup = process.env.VITEST_DISABLE_DOCKER_SETUP === 'true';
|
const skipDockerSetup = process.env.VITEST_DISABLE_DOCKER_SETUP === 'true';
|
||||||
@@ -24,5 +23,7 @@ export default defineConfig({
|
|||||||
maxWorkers: 1,
|
maxWorkers: 1,
|
||||||
isolate: false,
|
isolate: false,
|
||||||
},
|
},
|
||||||
plugins: [tsconfigPaths()],
|
resolve: {
|
||||||
|
tsconfigPaths: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import tsconfigPaths from 'vite-tsconfig-paths';
|
|
||||||
import { defineConfig } from 'vitest/config';
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
const skipDockerSetup = process.env.VITEST_DISABLE_DOCKER_SETUP === 'true';
|
const skipDockerSetup = process.env.VITEST_DISABLE_DOCKER_SETUP === 'true';
|
||||||
@@ -24,5 +23,7 @@ export default defineConfig({
|
|||||||
maxWorkers: 1,
|
maxWorkers: 1,
|
||||||
isolate: false,
|
isolate: false,
|
||||||
},
|
},
|
||||||
plugins: [tsconfigPaths()],
|
resolve: {
|
||||||
|
tsconfigPaths: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
+13
-1
@@ -5,8 +5,10 @@
|
|||||||
"acknowledge": "Neem kennis",
|
"acknowledge": "Neem kennis",
|
||||||
"action": "Aksie",
|
"action": "Aksie",
|
||||||
"action_common_update": "Werk by",
|
"action_common_update": "Werk by",
|
||||||
|
"action_description": "’n Stel van aksies om op die gefiltreerde bates uit te voer",
|
||||||
"actions": "Aksies",
|
"actions": "Aksies",
|
||||||
"active": "Aktief",
|
"active": "Aktief",
|
||||||
|
"active_count": "Aktief: {count}",
|
||||||
"activity": "Aktiwiteite",
|
"activity": "Aktiwiteite",
|
||||||
"activity_changed": "Aktiwiteit is {enabled, select, true {geaktiveer} other {gedeaktiveer}}",
|
"activity_changed": "Aktiwiteit is {enabled, select, true {geaktiveer} other {gedeaktiveer}}",
|
||||||
"add": "Voeg toe",
|
"add": "Voeg toe",
|
||||||
@@ -14,6 +16,9 @@
|
|||||||
"add_a_location": "Voeg ’n ligging toe",
|
"add_a_location": "Voeg ’n ligging toe",
|
||||||
"add_a_name": "Voeg ’n naam toe",
|
"add_a_name": "Voeg ’n naam toe",
|
||||||
"add_a_title": "Voeg ’n titel toe",
|
"add_a_title": "Voeg ’n titel toe",
|
||||||
|
"add_action": "Voeg aksie toe",
|
||||||
|
"add_action_description": "Klik om ’n aksie toe te voeg om uit te voer",
|
||||||
|
"add_assets": "Voeg bates by",
|
||||||
"add_birthday": "Voeg ’n verjaarsdag toe",
|
"add_birthday": "Voeg ’n verjaarsdag toe",
|
||||||
"add_endpoint": "Voeg eindpunt toe",
|
"add_endpoint": "Voeg eindpunt toe",
|
||||||
"add_exclusion_pattern": "Voeg uitsluitingspatroon toe",
|
"add_exclusion_pattern": "Voeg uitsluitingspatroon toe",
|
||||||
@@ -27,9 +32,13 @@
|
|||||||
"add_to_album": "Voeg toe tot album",
|
"add_to_album": "Voeg toe tot album",
|
||||||
"add_to_album_bottom_sheet_added": "Tot {album} toegevoeg",
|
"add_to_album_bottom_sheet_added": "Tot {album} toegevoeg",
|
||||||
"add_to_album_bottom_sheet_already_exists": "Reeds in {album}",
|
"add_to_album_bottom_sheet_already_exists": "Reeds in {album}",
|
||||||
|
"add_to_album_bottom_sheet_some_local_assets": "Sommige plaaslike bates kon nie toe gevoeg word tot die album nie",
|
||||||
|
"add_to_album_toggle": "Wissel seleksie vir {album}",
|
||||||
"add_to_albums": "Voeg toe tot albums",
|
"add_to_albums": "Voeg toe tot albums",
|
||||||
"add_to_albums_count": "Voeg toe tot albums ({count})",
|
"add_to_albums_count": "Voeg toe tot albums ({count})",
|
||||||
|
"add_to_bottom_bar": "Voeg toe",
|
||||||
"add_to_shared_album": "Voeg toe tot gedeelde album",
|
"add_to_shared_album": "Voeg toe tot gedeelde album",
|
||||||
|
"add_upload_to_stack": "Voeg oplaai by stapel",
|
||||||
"add_url": "Voeg bronadres toe",
|
"add_url": "Voeg bronadres toe",
|
||||||
"added_to_archive": "Tot argief toegevoeg",
|
"added_to_archive": "Tot argief toegevoeg",
|
||||||
"added_to_favorites": "Tot gunstelinge toegevoeg",
|
"added_to_favorites": "Tot gunstelinge toegevoeg",
|
||||||
@@ -46,6 +55,7 @@
|
|||||||
"backup_database": "Skep Databasisstortlêer",
|
"backup_database": "Skep Databasisstortlêer",
|
||||||
"backup_database_enable_description": "Aktiveer databasisstortlêers",
|
"backup_database_enable_description": "Aktiveer databasisstortlêers",
|
||||||
"backup_keep_last_amount": "Aantal vorige stortlêers om te hou",
|
"backup_keep_last_amount": "Aantal vorige stortlêers om te hou",
|
||||||
|
"backup_onboarding_2_description": "plaaslike kopieë op verskillende toestelle. Dit sluit die hooflêers en ’n rugsteun van daardie lêers plaaslik in.",
|
||||||
"backup_onboarding_3_description": "totale kopieë van u data, insluitend die oorspronklike lêers. Dit sluit 1 kopie op ’n ander perseel en 2 lokale kopieë in.",
|
"backup_onboarding_3_description": "totale kopieë van u data, insluitend die oorspronklike lêers. Dit sluit 1 kopie op ’n ander perseel en 2 lokale kopieë in.",
|
||||||
"backup_onboarding_description": "’n <backblaze-link>3-2-1-rugsteunstrategie</backblaze-link> word sterk aanbeveel om u data veilig te hou. Hou kopieë van u foto’s/video’s sowel as die Immich-databasis vir ’n volledige rugsteunoplossing.",
|
"backup_onboarding_description": "’n <backblaze-link>3-2-1-rugsteunstrategie</backblaze-link> word sterk aanbeveel om u data veilig te hou. Hou kopieë van u foto’s/video’s sowel as die Immich-databasis vir ’n volledige rugsteunoplossing.",
|
||||||
"backup_onboarding_footer": "Lees hierdie <link>dokument</link> vir meer inligting oor hoe om ’n rugsteunkopie van Immich te maak.",
|
"backup_onboarding_footer": "Lees hierdie <link>dokument</link> vir meer inligting oor hoe om ’n rugsteunkopie van Immich te maak.",
|
||||||
@@ -61,6 +71,7 @@
|
|||||||
"confirm_reprocess_all_faces": "Is u seker u wil alle gesigte herverwerk? Dit sal ook genoemde mense skoonmaak.",
|
"confirm_reprocess_all_faces": "Is u seker u wil alle gesigte herverwerk? Dit sal ook genoemde mense skoonmaak.",
|
||||||
"confirm_user_password_reset": "Is u seker u wil {user} se wagwoord terugstel?",
|
"confirm_user_password_reset": "Is u seker u wil {user} se wagwoord terugstel?",
|
||||||
"confirm_user_pin_code_reset": "Is u seker u wil {user} se PIN-kode herstel?",
|
"confirm_user_pin_code_reset": "Is u seker u wil {user} se PIN-kode herstel?",
|
||||||
|
"copy_config_to_clipboard_description": "Kopieer die huidige stelselkonfigurasie as ’n JSON‑objek na die klipbord",
|
||||||
"create_job": "Skep taak",
|
"create_job": "Skep taak",
|
||||||
"cron_expression": "Cron-uitdrukking",
|
"cron_expression": "Cron-uitdrukking",
|
||||||
"cron_expression_description": "Stel die skanderingsinterval in met die cron-formaat. Kyk gerus na bv. <link>Crontab Guru</link> vir meer inligting",
|
"cron_expression_description": "Stel die skanderingsinterval in met die cron-formaat. Kyk gerus na bv. <link>Crontab Guru</link> vir meer inligting",
|
||||||
@@ -68,6 +79,8 @@
|
|||||||
"disable_login": "Deaktiveer aantekening",
|
"disable_login": "Deaktiveer aantekening",
|
||||||
"duplicate_detection_job_description": "Begin masjienleer op items om soortgelyke beelde op te spoor. Maak staat op Slimsoek",
|
"duplicate_detection_job_description": "Begin masjienleer op items om soortgelyke beelde op te spoor. Maak staat op Slimsoek",
|
||||||
"exclusion_pattern_description": "Met uitsluitingspatrone kan u lêers en vouers ignoreer wanneer u u biblioteek skandeer. Dit is nuttig as u vouers het wat lêers bevat wat u nie wil invoer nie, soos RAW-lêers.",
|
"exclusion_pattern_description": "Met uitsluitingspatrone kan u lêers en vouers ignoreer wanneer u u biblioteek skandeer. Dit is nuttig as u vouers het wat lêers bevat wat u nie wil invoer nie, soos RAW-lêers.",
|
||||||
|
"export_config_as_json_description": "Laai die huidige stelselkonfigurasie af as ’n JSON‑lêer",
|
||||||
|
"external_libraries_page_description": "Admin eksterne biblioteekbladsy",
|
||||||
"face_detection": "Gesigherkenning",
|
"face_detection": "Gesigherkenning",
|
||||||
"face_detection_description": "Identifiseer die gesigte in media d.m.v. masjienleer. Vir video’s word slegs die duimnael oorweeg. “Herlaai” (ver)werk al die media weer. “Stel terug” verwyder alle huidige gesigdata. “Onverwerk” plaas items in die ry wat nog nie verwerk is nie. Geïdentifiseerde gesigte sal ná voltooiing van Gesigidentifikasie vir Gesigherkenning in die ry geplaas word om hulle in bestaande of nuwe persone te groepeer.",
|
"face_detection_description": "Identifiseer die gesigte in media d.m.v. masjienleer. Vir video’s word slegs die duimnael oorweeg. “Herlaai” (ver)werk al die media weer. “Stel terug” verwyder alle huidige gesigdata. “Onverwerk” plaas items in die ry wat nog nie verwerk is nie. Geïdentifiseerde gesigte sal ná voltooiing van Gesigidentifikasie vir Gesigherkenning in die ry geplaas word om hulle in bestaande of nuwe persone te groepeer.",
|
||||||
"facial_recognition_job_description": "Groepeer gesigte in mense. Die stap is vinniger nadat Gesigherkenning klaar is. “Herstel” (her-)groepeer alle gesigte. “Vermiste” plaas gesigte in ry wat nie ’n persoon gekoppel het nie.",
|
"facial_recognition_job_description": "Groepeer gesigte in mense. Die stap is vinniger nadat Gesigherkenning klaar is. “Herstel” (her-)groepeer alle gesigte. “Vermiste” plaas gesigte in ry wat nie ’n persoon gekoppel het nie.",
|
||||||
@@ -189,7 +202,6 @@
|
|||||||
"unsupported_field_type": "Onondersteunde veldtipe",
|
"unsupported_field_type": "Onondersteunde veldtipe",
|
||||||
"unsupported_file_type": "Lêer {file} kan nie opgelaai word nie omdat die lêertipe {type} nie ondersteun word nie.",
|
"unsupported_file_type": "Lêer {file} kan nie opgelaai word nie omdat die lêertipe {type} nie ondersteun word nie.",
|
||||||
"untagged": "Sonder etiket",
|
"untagged": "Sonder etiket",
|
||||||
"untitled_workflow": "Naamlose werkvloei",
|
|
||||||
"up_next": "Volgende",
|
"up_next": "Volgende",
|
||||||
"update_location_action_prompt": "Werk die ligging van {count} gekose items by met:",
|
"update_location_action_prompt": "Werk die ligging van {count} gekose items by met:",
|
||||||
"updated_at": "Bygewerk",
|
"updated_at": "Bygewerk",
|
||||||
|
|||||||
+112
-19
@@ -5,10 +5,10 @@
|
|||||||
"acknowledge": "أُدرك ذلك",
|
"acknowledge": "أُدرك ذلك",
|
||||||
"action": "إجراء",
|
"action": "إجراء",
|
||||||
"action_common_update": "تحديث",
|
"action_common_update": "تحديث",
|
||||||
"action_description": "مجموعة من الفعاليات التي ستنفذ على الأصول التي تم تصفيتها",
|
"action_description": "مجموعة إجراءات لتنفيذها على المحتويات المصفاة",
|
||||||
"actions": "عمليات",
|
"actions": "عمليات",
|
||||||
"active": "نشط",
|
"active": "نشط",
|
||||||
"active_count": "فعال: {count}",
|
"active_count": "نشط: {count}",
|
||||||
"activity": "نشاط",
|
"activity": "نشاط",
|
||||||
"activity_changed": "النشاط {enabled, select, true {مُفْعل} other {معطّل}}",
|
"activity_changed": "النشاط {enabled, select, true {مُفْعل} other {معطّل}}",
|
||||||
"add": "إضافة",
|
"add": "إضافة",
|
||||||
@@ -22,13 +22,12 @@
|
|||||||
"add_birthday": "أضف تاريخ الميلاد",
|
"add_birthday": "أضف تاريخ الميلاد",
|
||||||
"add_endpoint": "اضف نقطة نهاية",
|
"add_endpoint": "اضف نقطة نهاية",
|
||||||
"add_exclusion_pattern": "إضافة نمط إستثناء",
|
"add_exclusion_pattern": "إضافة نمط إستثناء",
|
||||||
"add_filter": "اضف تصفية",
|
|
||||||
"add_filter_description": "اضغط لاضافة شرط تصفية",
|
|
||||||
"add_location": "إضافة موقع",
|
"add_location": "إضافة موقع",
|
||||||
"add_more_users": "إضافة مستخدمين آخرين",
|
"add_more_users": "إضافة مستخدمين آخرين",
|
||||||
"add_partner": "أضف شريكًا",
|
"add_partner": "أضف شريكًا",
|
||||||
"add_path": "إضافة مسار",
|
"add_path": "إضافة مسار",
|
||||||
"add_photos": "إضافة صور",
|
"add_photos": "إضافة صور",
|
||||||
|
"add_step": "اضف خطوة",
|
||||||
"add_tag": "اضف علامة",
|
"add_tag": "اضف علامة",
|
||||||
"add_to": "إضافة إلى…",
|
"add_to": "إضافة إلى…",
|
||||||
"add_to_album": "إضافة إلى ألبوم",
|
"add_to_album": "إضافة إلى ألبوم",
|
||||||
@@ -42,7 +41,6 @@
|
|||||||
"add_to_shared_album": "إضافة إلى ألبوم مشارك",
|
"add_to_shared_album": "إضافة إلى ألبوم مشارك",
|
||||||
"add_upload_to_stack": "اضف رفع الى حزمة",
|
"add_upload_to_stack": "اضف رفع الى حزمة",
|
||||||
"add_url": "إضافة رابط",
|
"add_url": "إضافة رابط",
|
||||||
"add_workflow_step": "اضف خطوة سير عمل",
|
|
||||||
"added_to_archive": "أُضيفت للأرشيف",
|
"added_to_archive": "أُضيفت للأرشيف",
|
||||||
"added_to_favorites": "أُضيفت للمفضلات",
|
"added_to_favorites": "أُضيفت للمفضلات",
|
||||||
"added_to_favorites_count": "تم إضافة {count, number} إلى المفضلات",
|
"added_to_favorites_count": "تم إضافة {count, number} إلى المفضلات",
|
||||||
@@ -61,8 +59,8 @@
|
|||||||
"backup_onboarding_1_description": "نسخة خارج الموقع في موقع آخر.",
|
"backup_onboarding_1_description": "نسخة خارج الموقع في موقع آخر.",
|
||||||
"backup_onboarding_2_description": "نسخ محلية على أجهزة مختلفة. يشمل ذلك الملفات الرئيسية ونسخة احتياطية محلية منها.",
|
"backup_onboarding_2_description": "نسخ محلية على أجهزة مختلفة. يشمل ذلك الملفات الرئيسية ونسخة احتياطية محلية منها.",
|
||||||
"backup_onboarding_3_description": "إجمالي نُسخ بياناتك، بما في ذلك الملفات الأصلية. يشمل ذلك نسخةً واحدةً خارج الموقع ونسختين محليتين.",
|
"backup_onboarding_3_description": "إجمالي نُسخ بياناتك، بما في ذلك الملفات الأصلية. يشمل ذلك نسخةً واحدةً خارج الموقع ونسختين محليتين.",
|
||||||
"backup_onboarding_description": "يُنصح باتباع <backblaze-link>استراتيجية النسخ الاحتياطي 3-2- 1</backblaze-link> لحماية بياناتك. احتفظ بنسخ احتياطية من صورك/فيديوهاتك المحمّلة، بالإضافة إلى قاعدة بيانات Immich، لضمان حل نسخ احتياطي شامل.",
|
"backup_onboarding_description": "يُنصح باتباع استراتيجية النسخ الاحتياطي <backblaze-link>3-2-1 backup strategy</backblaze-link> لحماية بياناتك. يجب عليك الاحتفاظ بنسخ من الصور/مقاطع الفيديو المرفوعة بالإضافة إلى قاعدة بيانات Immich للحصول على حل نسخ احتياطي شامل.",
|
||||||
"backup_onboarding_footer": "لمزيد من المعلومات حول النسخ الاحتياطي لـ <link>Immich</link>، يرجى الرجوع إلى <link>الوثائق</link>.",
|
"backup_onboarding_footer": "لمزيد من المعلومات حول النسخ الاحتياطي لـ Immich، يرجى الرجوع إلى <link>الوثائق</link>.",
|
||||||
"backup_onboarding_parts_title": "يتضمن النسخ الاحتياطي 3-2-1 ما يلي:",
|
"backup_onboarding_parts_title": "يتضمن النسخ الاحتياطي 3-2-1 ما يلي:",
|
||||||
"backup_onboarding_title": "النسخ الاحتياطية",
|
"backup_onboarding_title": "النسخ الاحتياطية",
|
||||||
"backup_settings": "إعدادات تفريغ قاعدة البيانات",
|
"backup_settings": "إعدادات تفريغ قاعدة البيانات",
|
||||||
@@ -81,6 +79,7 @@
|
|||||||
"cron_expression_description": "اضبط الفاصل الزمني للفحص باستخدام تنسيق cron. لمزيد من المعلومات يُرجى الرجوع إلى <link>Crontab Guru</link> على سبيل المثال",
|
"cron_expression_description": "اضبط الفاصل الزمني للفحص باستخدام تنسيق cron. لمزيد من المعلومات يُرجى الرجوع إلى <link>Crontab Guru</link> على سبيل المثال",
|
||||||
"cron_expression_presets": "الإعدادات المسبقة لتعبير Cron",
|
"cron_expression_presets": "الإعدادات المسبقة لتعبير Cron",
|
||||||
"disable_login": "تعطيل تسجيل الدخول",
|
"disable_login": "تعطيل تسجيل الدخول",
|
||||||
|
"download_csv": "حمل CSV",
|
||||||
"duplicate_detection_job_description": "بدء التعلم الآلي على المحتوى للعثور على الصور المتشابهة. يعتمد على البحث الذكي",
|
"duplicate_detection_job_description": "بدء التعلم الآلي على المحتوى للعثور على الصور المتشابهة. يعتمد على البحث الذكي",
|
||||||
"exclusion_pattern_description": "تتيح لك أنماط الاستبعاد تجاهل الملفات والمجلدات عند فحص مكتبتك. يعد هذا مفيدًا إذا كان لديك مجلدات تحتوي على ملفات لا تريد استيرادها، مثل ملفات RAW.",
|
"exclusion_pattern_description": "تتيح لك أنماط الاستبعاد تجاهل الملفات والمجلدات عند فحص مكتبتك. يعد هذا مفيدًا إذا كان لديك مجلدات تحتوي على ملفات لا تريد استيرادها، مثل ملفات RAW.",
|
||||||
"export_config_as_json_description": "تحميل اعدادات النظام الحالية كملف بصيغة JSON",
|
"export_config_as_json_description": "تحميل اعدادات النظام الحالية كملف بصيغة JSON",
|
||||||
@@ -193,6 +192,10 @@
|
|||||||
"maintenance_delete_backup": "حذف النسخ الاحتياطي",
|
"maintenance_delete_backup": "حذف النسخ الاحتياطي",
|
||||||
"maintenance_delete_backup_description": "هذا الملف سيتم حذفه بشكل لا رجعه فيه.",
|
"maintenance_delete_backup_description": "هذا الملف سيتم حذفه بشكل لا رجعه فيه.",
|
||||||
"maintenance_delete_error": "فشل حذف النسخ الاحتياطي.",
|
"maintenance_delete_error": "فشل حذف النسخ الاحتياطي.",
|
||||||
|
"maintenance_integrity_check_all": "تحديد الكل",
|
||||||
|
"maintenance_integrity_checksum_mismatch": "عدم تطابق رمز التحقق",
|
||||||
|
"maintenance_integrity_missing_file": "الملفات المفقودة",
|
||||||
|
"maintenance_integrity_missing_file_job": "التحقق من الملفات المفقودة",
|
||||||
"maintenance_restore_backup": "استعادة النسخ الاحتياطي",
|
"maintenance_restore_backup": "استعادة النسخ الاحتياطي",
|
||||||
"maintenance_restore_backup_description": "سيتم مسح بيانات Immich واستعادتها من النسخة الاحتياطي المختار. سيتم إنشاء نسخة احتياطية قبل المتابعة.",
|
"maintenance_restore_backup_description": "سيتم مسح بيانات Immich واستعادتها من النسخة الاحتياطي المختار. سيتم إنشاء نسخة احتياطية قبل المتابعة.",
|
||||||
"maintenance_restore_backup_different_version": "هذا النسخ الاحتياطي تم انشائه باستخدام اصدار مختلف من Immich!",
|
"maintenance_restore_backup_different_version": "هذا النسخ الاحتياطي تم انشائه باستخدام اصدار مختلف من Immich!",
|
||||||
@@ -267,6 +270,8 @@
|
|||||||
"notification_enable_email_notifications": "تفعيل إشعارات البريد الإلكتروني",
|
"notification_enable_email_notifications": "تفعيل إشعارات البريد الإلكتروني",
|
||||||
"notification_settings": "إعدادات الإشعارات",
|
"notification_settings": "إعدادات الإشعارات",
|
||||||
"notification_settings_description": "إدارة إعدادات الإشعارات، بما في ذلك البريد الإلكتروني",
|
"notification_settings_description": "إدارة إعدادات الإشعارات، بما في ذلك البريد الإلكتروني",
|
||||||
|
"oauth_allow_insecure_requests": "السماح بالطلبات الغير الآمنة",
|
||||||
|
"oauth_allow_insecure_requests_description": "تحذير: هذا يعطل التحقق من صحة شهادة أمن طبقة النقل لطلبات الترخيص المفتوح وقد يعرضك الهجمات الوسطية.",
|
||||||
"oauth_auto_launch": "التشغيل التلقائي",
|
"oauth_auto_launch": "التشغيل التلقائي",
|
||||||
"oauth_auto_launch_description": "ابدأ تدفق تسجيل الدخول OAuth تلقائيًا عند الانتقال إلى صفحة تسجيل الدخول",
|
"oauth_auto_launch_description": "ابدأ تدفق تسجيل الدخول OAuth تلقائيًا عند الانتقال إلى صفحة تسجيل الدخول",
|
||||||
"oauth_auto_register": "التسجيل التلقائي",
|
"oauth_auto_register": "التسجيل التلقائي",
|
||||||
@@ -274,9 +279,11 @@
|
|||||||
"oauth_button_text": "نص الزر",
|
"oauth_button_text": "نص الزر",
|
||||||
"oauth_client_secret_description": "مطلوب للعميل السري، او اذا PKCE(مفتاح الاثبات لتبادل الكود) ليس مدعوم من العميل العام.",
|
"oauth_client_secret_description": "مطلوب للعميل السري، او اذا PKCE(مفتاح الاثبات لتبادل الكود) ليس مدعوم من العميل العام.",
|
||||||
"oauth_enable_description": "تسجيل الدخول باستخدام OAuth",
|
"oauth_enable_description": "تسجيل الدخول باستخدام OAuth",
|
||||||
|
"oauth_end_session_url_description": "إعادة توجيه المستخدم إلى معرف الموارد الموحد (URI) هذا عند تسجيل الخروج.",
|
||||||
"oauth_mobile_redirect_uri": "عنوان URI لإعادة التوجيه على الهاتف",
|
"oauth_mobile_redirect_uri": "عنوان URI لإعادة التوجيه على الهاتف",
|
||||||
"oauth_mobile_redirect_uri_override": "تجاوز عنوان URI لإعادة التوجيه على الهاتف",
|
"oauth_mobile_redirect_uri_override": "تجاوز عنوان URI لإعادة التوجيه على الهاتف",
|
||||||
"oauth_mobile_redirect_uri_override_description": "قم بتفعيله عندما لا يسمح موفر OAuth بمعرف URI للجوال، مثل ''{callback}''",
|
"oauth_mobile_redirect_uri_override_description": "قم بتفعيله عندما لا يسمح موفر OAuth بمعرف URI للجوال، مثل ''{callback}''",
|
||||||
|
"oauth_prompt_description": "مُعامل التوجيه (مثلselect_account, login, consent)",
|
||||||
"oauth_role_claim": "المطالبة بالدور(صلاحيات)",
|
"oauth_role_claim": "المطالبة بالدور(صلاحيات)",
|
||||||
"oauth_role_claim_description": "منح وصول المسؤول تلقائيًا بناءً على وجود هذا الطلب. قد يكون الطلب إما 'مستخدم' أو 'مسؤول'.",
|
"oauth_role_claim_description": "منح وصول المسؤول تلقائيًا بناءً على وجود هذا الطلب. قد يكون الطلب إما 'مستخدم' أو 'مسؤول'.",
|
||||||
"oauth_settings": "OAuth",
|
"oauth_settings": "OAuth",
|
||||||
@@ -303,6 +310,8 @@
|
|||||||
"refreshing_all_libraries": "تحديث كافة المكتبات",
|
"refreshing_all_libraries": "تحديث كافة المكتبات",
|
||||||
"registration": "تسجيل المدير",
|
"registration": "تسجيل المدير",
|
||||||
"registration_description": "بما أنك أول مستخدم في النظام، سيتم تعيينك كمسؤول وستكون مسؤولًا عن المهام الإدارية، وسيتم إنشاء مستخدمين إضافيين بواسطتك.",
|
"registration_description": "بما أنك أول مستخدم في النظام، سيتم تعيينك كمسؤول وستكون مسؤولًا عن المهام الإدارية، وسيتم إنشاء مستخدمين إضافيين بواسطتك.",
|
||||||
|
"release_channel_release_candidate": "إصدار مرشح",
|
||||||
|
"release_channel_stable": "مستقر",
|
||||||
"remove_failed_jobs": "ازالة العمليات التي فشلت",
|
"remove_failed_jobs": "ازالة العمليات التي فشلت",
|
||||||
"require_password_change_on_login": "الطلب من المستخدم تغيير كلمة المرور عند تسجيل الدخول الأول",
|
"require_password_change_on_login": "الطلب من المستخدم تغيير كلمة المرور عند تسجيل الدخول الأول",
|
||||||
"reset_settings_to_default": "إعادة ضبط الإعدادات إلى الوضع الافتراضي",
|
"reset_settings_to_default": "إعادة ضبط الإعدادات إلى الوضع الافتراضي",
|
||||||
@@ -333,7 +342,7 @@
|
|||||||
"storage_template_migration_description": "قم بتطبيق القالب الحالي <link>{template}</link> على المحتويات التي تم رفعها سابقًا",
|
"storage_template_migration_description": "قم بتطبيق القالب الحالي <link>{template}</link> على المحتويات التي تم رفعها سابقًا",
|
||||||
"storage_template_migration_info": "تغييرات النموذج الخزني ستغير جميع الصيغ الى احرف صغيرة. تغييرات النموذج ستنطبق فقط على المحتويات الجديدة. لتطبيق النموذج على المحتويات التي تم رفعها سابقًا، قم بتشغيل <link>{job}</link>.",
|
"storage_template_migration_info": "تغييرات النموذج الخزني ستغير جميع الصيغ الى احرف صغيرة. تغييرات النموذج ستنطبق فقط على المحتويات الجديدة. لتطبيق النموذج على المحتويات التي تم رفعها سابقًا، قم بتشغيل <link>{job}</link>.",
|
||||||
"storage_template_migration_job": "وظيفة تهجير قالب التخزين",
|
"storage_template_migration_job": "وظيفة تهجير قالب التخزين",
|
||||||
"storage_template_more_details": "لمزيد من التفاصيل حول هذه الميزة، يرجى الرجوع إلى <template-link>Storage Template</template-link> و <implications-link>implications</implications-link>.",
|
"storage_template_more_details": "لمزيد من التفاصيل حول هذه الميزة، يرجى الرجوع إلى <template-link>Storage Template</template-link> و <implications-link>implications</implications-link>",
|
||||||
"storage_template_onboarding_description_v2": "عند التفعيل. هذه الخاصية ستقوم بالترتيب التلقائي للملفات بناء على نموذج معرف من قبل المستخدم. رجاء اطلع على <link>التوثيق</link>.",
|
"storage_template_onboarding_description_v2": "عند التفعيل. هذه الخاصية ستقوم بالترتيب التلقائي للملفات بناء على نموذج معرف من قبل المستخدم. رجاء اطلع على <link>التوثيق</link>.",
|
||||||
"storage_template_path_length": "الحد التقريبي لطول المسار: <b>{length, number}</b>/{limit, number}",
|
"storage_template_path_length": "الحد التقريبي لطول المسار: <b>{length, number}</b>/{limit, number}",
|
||||||
"storage_template_settings": "قالب التخزين",
|
"storage_template_settings": "قالب التخزين",
|
||||||
@@ -397,6 +406,10 @@
|
|||||||
"transcoding_preferred_hardware_device_description": "ينطبق فقط على VAAPI وQSV. يضبط عقدة dri المستخدمة لتحويل ترميز الأجهزة.",
|
"transcoding_preferred_hardware_device_description": "ينطبق فقط على VAAPI وQSV. يضبط عقدة dri المستخدمة لتحويل ترميز الأجهزة.",
|
||||||
"transcoding_preset_preset": "الضبط المُسبق (-preset)",
|
"transcoding_preset_preset": "الضبط المُسبق (-preset)",
|
||||||
"transcoding_preset_preset_description": "سرعة الضغط. تؤدي الإعدادات المسبقة الأبطأ إلى إنتاج ملفات أصغر حجمًا، وزيادة الجودة عند استهداف معدل بت معين. يتجاهل VP9 السرعات الأعلى من 'الأسرع'.",
|
"transcoding_preset_preset_description": "سرعة الضغط. تؤدي الإعدادات المسبقة الأبطأ إلى إنتاج ملفات أصغر حجمًا، وزيادة الجودة عند استهداف معدل بت معين. يتجاهل VP9 السرعات الأعلى من 'الأسرع'.",
|
||||||
|
"transcoding_realtime": "تحويل الترميز في الوقت الفعلي [تجريبي]",
|
||||||
|
"transcoding_realtime_description": "يتيح إجراء تحويل الترميز في الوقت الفعلي أثناء بث الفيديو. كما يمكّن من تبديل الجودة، ولكنه قد يتسبب في زيادة زمن تأخير التشغيل وحدوث تقطيع، اعتماداً على قدرات الخادم.",
|
||||||
|
"transcoding_realtime_enabled": "تفعيل تحويل الترميز في الوقت الفعلي",
|
||||||
|
"transcoding_realtime_enabled_description": "في حال تعطيله، سيرفض الخادم بدء جلسات تحويل ترميز جديدة في الوقت الفعلي.",
|
||||||
"transcoding_reference_frames": "الإطارات المرجعية",
|
"transcoding_reference_frames": "الإطارات المرجعية",
|
||||||
"transcoding_reference_frames_description": "عدد الإطارات التي يجب الرجوع إليها عند ضغط إطار معين. تعمل القيم الأعلى على تحسين كفاءة الضغط، ولكنها تبطئ عملية التشفير. 0 يضبط هذه القيمة تلقائيًا.",
|
"transcoding_reference_frames_description": "عدد الإطارات التي يجب الرجوع إليها عند ضغط إطار معين. تعمل القيم الأعلى على تحسين كفاءة الضغط، ولكنها تبطئ عملية التشفير. 0 يضبط هذه القيمة تلقائيًا.",
|
||||||
"transcoding_required_description": "فقط مقاطع الفيديو ذات التنسيق غير المقبول",
|
"transcoding_required_description": "فقط مقاطع الفيديو ذات التنسيق غير المقبول",
|
||||||
@@ -440,6 +453,8 @@
|
|||||||
"user_settings_description": "إدارة إعدادات المستخدم",
|
"user_settings_description": "إدارة إعدادات المستخدم",
|
||||||
"user_successfully_removed": "المستخدم {email} تمت ازالته بنجاح.",
|
"user_successfully_removed": "المستخدم {email} تمت ازالته بنجاح.",
|
||||||
"users_page_description": "صفحة ادارة المستخدمين",
|
"users_page_description": "صفحة ادارة المستخدمين",
|
||||||
|
"version_check_channel": "قناة الإصدار",
|
||||||
|
"version_check_channel_description": "اختر قناة الإصدار التي ترغب في تلقي إعلانات الإصدارات لها",
|
||||||
"version_check_enabled_description": "تفعيل التحقق من الإصدارات الجديدة",
|
"version_check_enabled_description": "تفعيل التحقق من الإصدارات الجديدة",
|
||||||
"version_check_implications": "تعتمد ميزة التحقق من الإصدار على التواصل الدوري مع {server}",
|
"version_check_implications": "تعتمد ميزة التحقق من الإصدار على التواصل الدوري مع {server}",
|
||||||
"version_check_settings": "التحقق من الإصدار",
|
"version_check_settings": "التحقق من الإصدار",
|
||||||
@@ -566,7 +581,7 @@
|
|||||||
"asset_hashing": "التجزئة…",
|
"asset_hashing": "التجزئة…",
|
||||||
"asset_list_group_by_sub_title": "تنظيم بواسطة",
|
"asset_list_group_by_sub_title": "تنظيم بواسطة",
|
||||||
"asset_list_layout_settings_dynamic_layout_title": "تخطيط ديناميكي",
|
"asset_list_layout_settings_dynamic_layout_title": "تخطيط ديناميكي",
|
||||||
"asset_list_layout_settings_group_automatically": "تلقائي",
|
"asset_list_layout_settings_group_automatically": "تلقائيا",
|
||||||
"asset_list_layout_settings_group_by": "مجموعة الأصول حسب",
|
"asset_list_layout_settings_group_by": "مجموعة الأصول حسب",
|
||||||
"asset_list_layout_settings_group_by_month_day": "شهر + يوم",
|
"asset_list_layout_settings_group_by_month_day": "شهر + يوم",
|
||||||
"asset_list_layout_sub_title": "تصميم",
|
"asset_list_layout_sub_title": "تصميم",
|
||||||
@@ -696,6 +711,7 @@
|
|||||||
"birthdate_saved": "تم حفظ تاريخ الميلاد بنجاح",
|
"birthdate_saved": "تم حفظ تاريخ الميلاد بنجاح",
|
||||||
"birthdate_set_description": "يتم استخدام تاريخ الميلاد لحساب عمر هذا الشخص وقت التقاط الصورة.",
|
"birthdate_set_description": "يتم استخدام تاريخ الميلاد لحساب عمر هذا الشخص وقت التقاط الصورة.",
|
||||||
"blurred_background": "خلفية مشوشة",
|
"blurred_background": "خلفية مشوشة",
|
||||||
|
"browse_templates": "تصفح القوالب",
|
||||||
"bugs_and_feature_requests": "الأخطاء وطلبات الميزات",
|
"bugs_and_feature_requests": "الأخطاء وطلبات الميزات",
|
||||||
"build": "يبني",
|
"build": "يبني",
|
||||||
"build_image": "بناء الصورة",
|
"build_image": "بناء الصورة",
|
||||||
@@ -729,6 +745,7 @@
|
|||||||
"cannot_update_the_description": "لا يمكن تحديث الوصف",
|
"cannot_update_the_description": "لا يمكن تحديث الوصف",
|
||||||
"cast": "بث",
|
"cast": "بث",
|
||||||
"cast_description": "ضبط وجهات البث المتوفرة",
|
"cast_description": "ضبط وجهات البث المتوفرة",
|
||||||
|
"change": "تغيير",
|
||||||
"change_date": "غيّر التاريخ",
|
"change_date": "غيّر التاريخ",
|
||||||
"change_description": "تغيير الوصف",
|
"change_description": "تغيير الوصف",
|
||||||
"change_display_order": "تغيير ترتيب العرض",
|
"change_display_order": "تغيير ترتيب العرض",
|
||||||
@@ -757,6 +774,7 @@
|
|||||||
"check_corrupt_asset_backup_description": "قم بإجراء هذا الفحص فقط عبر شبكة Wi-Fi وبعد نسخ جميع الأصول احتياطيًا. قد يستغرق الإجراء بضع دقائق.",
|
"check_corrupt_asset_backup_description": "قم بإجراء هذا الفحص فقط عبر شبكة Wi-Fi وبعد نسخ جميع الأصول احتياطيًا. قد يستغرق الإجراء بضع دقائق.",
|
||||||
"check_logs": "تحقق من السجلات",
|
"check_logs": "تحقق من السجلات",
|
||||||
"checksum": "مجموع التحقق",
|
"checksum": "مجموع التحقق",
|
||||||
|
"choose": "اختيار",
|
||||||
"choose_matching_people_to_merge": "اختر الأشخاص المتطابقين لدمجهم",
|
"choose_matching_people_to_merge": "اختر الأشخاص المتطابقين لدمجهم",
|
||||||
"city": "المدينة",
|
"city": "المدينة",
|
||||||
"cleanup_confirm_description": "Immich وجد {count} اصول (انشئت قبل {date}) تم خزنها احتياطيا الى الخادم. ازالة النسخ المحلية من هذا الجهاز?",
|
"cleanup_confirm_description": "Immich وجد {count} اصول (انشئت قبل {date}) تم خزنها احتياطيا الى الخادم. ازالة النسخ المحلية من هذا الجهاز?",
|
||||||
@@ -774,6 +792,7 @@
|
|||||||
"clear": "إخلاء",
|
"clear": "إخلاء",
|
||||||
"clear_all": "إخلاء الكل",
|
"clear_all": "إخلاء الكل",
|
||||||
"clear_all_recent_searches": "مسح جميع عمليات البحث الأخيرة",
|
"clear_all_recent_searches": "مسح جميع عمليات البحث الأخيرة",
|
||||||
|
"clear_failed_count": "فشل المسح ({count})",
|
||||||
"clear_file_cache": "مسح ذاكرة التخزين المؤقت للملفات",
|
"clear_file_cache": "مسح ذاكرة التخزين المؤقت للملفات",
|
||||||
"clear_message": "إخلاء الرسالة",
|
"clear_message": "إخلاء الرسالة",
|
||||||
"clear_value": "إخلاء القيمة",
|
"clear_value": "إخلاء القيمة",
|
||||||
@@ -805,6 +824,7 @@
|
|||||||
"comments_are_disabled": "التعليقات معطلة",
|
"comments_are_disabled": "التعليقات معطلة",
|
||||||
"common_create_new_album": "إنشاء ألبوم جديد",
|
"common_create_new_album": "إنشاء ألبوم جديد",
|
||||||
"completed": "اكتمل",
|
"completed": "اكتمل",
|
||||||
|
"configuration": "اعدادات",
|
||||||
"confirm": "تأكيد",
|
"confirm": "تأكيد",
|
||||||
"confirm_admin_password": "تأكيد كلمة مرور المسؤول",
|
"confirm_admin_password": "تأكيد كلمة مرور المسؤول",
|
||||||
"confirm_delete_face": "هل أنت متأكد من حذف وجه {name} من الأصول؟",
|
"confirm_delete_face": "هل أنت متأكد من حذف وجه {name} من الأصول؟",
|
||||||
@@ -819,6 +839,7 @@
|
|||||||
"contain": "محتواة",
|
"contain": "محتواة",
|
||||||
"context": "السياق",
|
"context": "السياق",
|
||||||
"continue": "متابعة",
|
"continue": "متابعة",
|
||||||
|
"control_bottom_app_bar_add_tags": "اضافة علامات",
|
||||||
"control_bottom_app_bar_create_new_album": "إنشاء ألبوم جديد",
|
"control_bottom_app_bar_create_new_album": "إنشاء ألبوم جديد",
|
||||||
"control_bottom_app_bar_delete_from_immich": "حذف من Immich",
|
"control_bottom_app_bar_delete_from_immich": "حذف من Immich",
|
||||||
"control_bottom_app_bar_delete_from_local": "حذف من الجهاز",
|
"control_bottom_app_bar_delete_from_local": "حذف من الجهاز",
|
||||||
@@ -832,6 +853,7 @@
|
|||||||
"copy_error": "نسخ الخطأ",
|
"copy_error": "نسخ الخطأ",
|
||||||
"copy_file_path": "نسخ مسار الملف",
|
"copy_file_path": "نسخ مسار الملف",
|
||||||
"copy_image": "نسخ الصورة",
|
"copy_image": "نسخ الصورة",
|
||||||
|
"copy_json": "نسخ JSON",
|
||||||
"copy_link": "نسخ الرابط",
|
"copy_link": "نسخ الرابط",
|
||||||
"copy_link_to_clipboard": "انسخ الرابط إلى الحافظة",
|
"copy_link_to_clipboard": "انسخ الرابط إلى الحافظة",
|
||||||
"copy_password": "نسخ كلمة المرور",
|
"copy_password": "نسخ كلمة المرور",
|
||||||
@@ -881,17 +903,16 @@
|
|||||||
"cutoff_date_description": "احتفظ بالصور من آخر…",
|
"cutoff_date_description": "احتفظ بالصور من آخر…",
|
||||||
"cutoff_day": "{count, plural, one {يوم} other {ايام}}",
|
"cutoff_day": "{count, plural, one {يوم} other {ايام}}",
|
||||||
"cutoff_year": "{count, plural, one {سنة} other {سنوات}}",
|
"cutoff_year": "{count, plural, one {سنة} other {سنوات}}",
|
||||||
"daily_title_text_date": "E ، MMM DD",
|
|
||||||
"daily_title_text_date_year": "E ، MMM DD ، yyyy",
|
|
||||||
"dark": "معتم",
|
"dark": "معتم",
|
||||||
"dark_theme": "تبديل المظهر إلى الداكن",
|
"dark_theme": "تبديل المظهر إلى الداكن",
|
||||||
"date": "تاريخ",
|
"date": "تاريخ",
|
||||||
"date_after": "التارخ بعد",
|
"date_after": "التارخ بعد",
|
||||||
"date_and_time": "التاريخ و الوقت",
|
"date_and_time": "التاريخ و الوقت",
|
||||||
"date_before": "التاريخ قبل",
|
"date_before": "التاريخ قبل",
|
||||||
"date_format": "E ، Lll D ، Y • H: MM A",
|
"date_of_birth": "تاريخ الميلاد",
|
||||||
"date_of_birth_saved": "تم حفظ تاريخ الميلاد بنجاح",
|
"date_of_birth_saved": "تم حفظ تاريخ الميلاد بنجاح",
|
||||||
"date_range": "نطاق الموعد",
|
"date_range": "نطاق الموعد",
|
||||||
|
"date_time_original": "تاريخ/وقت الاصلي",
|
||||||
"day": "يوم",
|
"day": "يوم",
|
||||||
"days": "ايام",
|
"days": "ايام",
|
||||||
"deduplicate_all": "إلغاء تكرار الكل",
|
"deduplicate_all": "إلغاء تكرار الكل",
|
||||||
@@ -970,7 +991,10 @@
|
|||||||
"downloading_asset_filename": "جاري تنزيل الاصل {filename}",
|
"downloading_asset_filename": "جاري تنزيل الاصل {filename}",
|
||||||
"downloading_from_icloud": "التنزيل من iCloud",
|
"downloading_from_icloud": "التنزيل من iCloud",
|
||||||
"downloading_media": "تنزيل الوسائط",
|
"downloading_media": "تنزيل الوسائط",
|
||||||
|
"drag_to_reorder": "اسحب لإعادة الترتيب",
|
||||||
"drop_files_to_upload": "قم بإسقاط الملفات في أي مكان لرفعها",
|
"drop_files_to_upload": "قم بإسقاط الملفات في أي مكان لرفعها",
|
||||||
|
"duplicate": "تكرار",
|
||||||
|
"duplicate_workflow": "تكرار سير العمل",
|
||||||
"duplicates": "التكرارات",
|
"duplicates": "التكرارات",
|
||||||
"duplicates_description": "قم بحل كل مجموعة من خلال الإشارة إلى التكرارات، إن وجدت.",
|
"duplicates_description": "قم بحل كل مجموعة من خلال الإشارة إلى التكرارات، إن وجدت.",
|
||||||
"duration": "المدة",
|
"duration": "المدة",
|
||||||
@@ -1072,6 +1096,7 @@
|
|||||||
"failed_to_remove_product_key": "تعذر إزالة مفتاح المنتج",
|
"failed_to_remove_product_key": "تعذر إزالة مفتاح المنتج",
|
||||||
"failed_to_reset_pin_code": "فشل اعادة تعيين رمز الPIN",
|
"failed_to_reset_pin_code": "فشل اعادة تعيين رمز الPIN",
|
||||||
"failed_to_stack_assets": "فشل في تكديس المحتويات",
|
"failed_to_stack_assets": "فشل في تكديس المحتويات",
|
||||||
|
"failed_to_tag_assets": "فشل في وضع علامات على الأصول",
|
||||||
"failed_to_unstack_assets": "فشل في فصل المحتويات",
|
"failed_to_unstack_assets": "فشل في فصل المحتويات",
|
||||||
"failed_to_update_notification_status": "فشل في تحديث حالة الإشعار",
|
"failed_to_update_notification_status": "فشل في تحديث حالة الإشعار",
|
||||||
"incorrect_email_or_password": "بريد أو كلمة مرور غير صحيحة",
|
"incorrect_email_or_password": "بريد أو كلمة مرور غير صحيحة",
|
||||||
@@ -1191,11 +1216,13 @@
|
|||||||
"export_as_json": "تصدير كـ JSON",
|
"export_as_json": "تصدير كـ JSON",
|
||||||
"export_database": "تصدير قاعدة البيانات",
|
"export_database": "تصدير قاعدة البيانات",
|
||||||
"export_database_description": "تصدير قاعدة البيانات من نوع SQLite",
|
"export_database_description": "تصدير قاعدة البيانات من نوع SQLite",
|
||||||
|
"exposure_time": "وقت التعرض",
|
||||||
"extension": "الإمتداد",
|
"extension": "الإمتداد",
|
||||||
"external": "خارجي",
|
"external": "خارجي",
|
||||||
"external_libraries": "المكتبات الخارجية",
|
"external_libraries": "المكتبات الخارجية",
|
||||||
"external_network": "شبكة خارجية",
|
"external_network": "شبكة خارجية",
|
||||||
"external_network_sheet_info": "عندما لا يتواجد على شبكة Wi-Fi المفضلة، فإنه سيتصل بالخادم من خلال أول عناوين URL أدناه التي يمكنه الوصول إليها، بدءًا من الأعلى إلى الأسفل",
|
"external_network_sheet_info": "عندما لا يتواجد على شبكة Wi-Fi المفضلة، فإنه سيتصل بالخادم من خلال أول عناوين URL أدناه التي يمكنه الوصول إليها، بدءًا من الأعلى إلى الأسفل",
|
||||||
|
"f_number": "الفتحة البؤرية",
|
||||||
"face_unassigned": "غير معين",
|
"face_unassigned": "غير معين",
|
||||||
"failed": "فشل",
|
"failed": "فشل",
|
||||||
"failed_count": "فشل: {count}",
|
"failed_count": "فشل: {count}",
|
||||||
@@ -1213,7 +1240,6 @@
|
|||||||
"features_setting_description": "إدارة ميزات التطبيق",
|
"features_setting_description": "إدارة ميزات التطبيق",
|
||||||
"file_name_or_extension": "اسم الملف أو امتداده",
|
"file_name_or_extension": "اسم الملف أو امتداده",
|
||||||
"file_name_text": "أسم الملف",
|
"file_name_text": "أسم الملف",
|
||||||
"file_name_with_value": "اسم الملف: {file_name}",
|
|
||||||
"file_size": "حجم الملف",
|
"file_size": "حجم الملف",
|
||||||
"filename": "اسم الملف",
|
"filename": "اسم الملف",
|
||||||
"filetype": "نوع الملف",
|
"filetype": "نوع الملف",
|
||||||
@@ -1226,6 +1252,7 @@
|
|||||||
"find_them_fast": "يمكنك العثور عليها بسرعة بالاسم من خلال البحث",
|
"find_them_fast": "يمكنك العثور عليها بسرعة بالاسم من خلال البحث",
|
||||||
"first": "الاول",
|
"first": "الاول",
|
||||||
"fix_incorrect_match": "إصلاح المطابقة غير الصحيحة",
|
"fix_incorrect_match": "إصلاح المطابقة غير الصحيحة",
|
||||||
|
"focal_length": "البعد البؤري",
|
||||||
"folder": "مجلد",
|
"folder": "مجلد",
|
||||||
"folder_not_found": "لم يتم العثور على المجلد",
|
"folder_not_found": "لم يتم العثور على المجلد",
|
||||||
"folders": "المجلدات",
|
"folders": "المجلدات",
|
||||||
@@ -1236,6 +1263,7 @@
|
|||||||
"free_up_space_description": "نقل الصور والفديوات التي تم خزنها احتياطياالى سلة المهملات الخاصه بجهازك لتحرير المساحة. نسخك على اىخادم ستبقى بأمان.",
|
"free_up_space_description": "نقل الصور والفديوات التي تم خزنها احتياطياالى سلة المهملات الخاصه بجهازك لتحرير المساحة. نسخك على اىخادم ستبقى بأمان.",
|
||||||
"free_up_space_settings_subtitle": "تحرير خزن الجهاز",
|
"free_up_space_settings_subtitle": "تحرير خزن الجهاز",
|
||||||
"full_path": "مسار كامل:{path}",
|
"full_path": "مسار كامل:{path}",
|
||||||
|
"full_path_or_folder": "المسار الكامل او المجلد",
|
||||||
"gcast_enabled": "كوكل كاست",
|
"gcast_enabled": "كوكل كاست",
|
||||||
"gcast_enabled_description": "تقوم هذه الميزة بتحميل الموارد الخارجية من Google حتى تعمل.",
|
"gcast_enabled_description": "تقوم هذه الميزة بتحميل الموارد الخارجية من Google حتى تعمل.",
|
||||||
"general": "عام",
|
"general": "عام",
|
||||||
@@ -1345,6 +1373,7 @@
|
|||||||
"ios_debug_info_no_sync_yet": "لم يتم تشغيل أي مهمة مزامنة في الخلفية حتى الآن",
|
"ios_debug_info_no_sync_yet": "لم يتم تشغيل أي مهمة مزامنة في الخلفية حتى الآن",
|
||||||
"ios_debug_info_processes_queued": "{count, plural, one {{count} عملية خلفية ادخلتةفي طابور} other {{count} عمليات خلفية ادخلت في طابور}}",
|
"ios_debug_info_processes_queued": "{count, plural, one {{count} عملية خلفية ادخلتةفي طابور} other {{count} عمليات خلفية ادخلت في طابور}}",
|
||||||
"ios_debug_info_processing_ran_at": "المعالجة جرت في {dateTime}",
|
"ios_debug_info_processing_ran_at": "المعالجة جرت في {dateTime}",
|
||||||
|
"iso": "ISO",
|
||||||
"items_count": "{count, plural, one {# عنصر} other {# عناصر}}",
|
"items_count": "{count, plural, one {# عنصر} other {# عناصر}}",
|
||||||
"jobs": "الوظائف",
|
"jobs": "الوظائف",
|
||||||
"json_editor": "محرر JSON",
|
"json_editor": "محرر JSON",
|
||||||
@@ -1392,11 +1421,13 @@
|
|||||||
"light_theme": "التبديل إلى المظهر الفاتح",
|
"light_theme": "التبديل إلى المظهر الفاتح",
|
||||||
"like": "اعجاب",
|
"like": "اعجاب",
|
||||||
"like_deleted": "تم حذف الإعجاب",
|
"like_deleted": "تم حذف الإعجاب",
|
||||||
|
"link": "رابط",
|
||||||
"link_motion_video": "رابط فيديو الحركة",
|
"link_motion_video": "رابط فيديو الحركة",
|
||||||
"link_to_docs": "لمزيد من المعلومات، يُرجى الرجوع إلى <link>الوثائق</link>.",
|
"link_to_docs": "لمزيد من المعلومات، يُرجى الرجوع إلى <link>الوثائق</link>.",
|
||||||
"link_to_oauth": "الربط مع OAuth",
|
"link_to_oauth": "الربط مع OAuth",
|
||||||
"linked_oauth_account": "حساب مرتبط بـ OAuth",
|
"linked_oauth_account": "حساب مرتبط بـ OAuth",
|
||||||
"list": "قائمة",
|
"list": "قائمة",
|
||||||
|
"live": "حي",
|
||||||
"loading": "تحميل",
|
"loading": "تحميل",
|
||||||
"loading_search_results_failed": "فشل تحميل نتائج البحث",
|
"loading_search_results_failed": "فشل تحميل نتائج البحث",
|
||||||
"local": "محلّي",
|
"local": "محلّي",
|
||||||
@@ -1518,6 +1549,38 @@
|
|||||||
"marked_all_as_read": "تم تحديد الكل كمقروء",
|
"marked_all_as_read": "تم تحديد الكل كمقروء",
|
||||||
"matches": "تطابقات",
|
"matches": "تطابقات",
|
||||||
"matching_assets": "الاصول المطابقة",
|
"matching_assets": "الاصول المطابقة",
|
||||||
|
"media_chrome": {
|
||||||
|
"auto": "تلقائي",
|
||||||
|
"captions": "التعليقات التوضيحية",
|
||||||
|
"captions_off": "اطفاء",
|
||||||
|
"closed_captions": "الترجمة المرئية المغلقة",
|
||||||
|
"decode_error": "فشل فك التشفير",
|
||||||
|
"disable_captions": "تعطيل الترجمة",
|
||||||
|
"enable_captions": "تفعيل الترجمة",
|
||||||
|
"enter_fullscreen_mode": "ادخل إلى وضع ملء الشاشة",
|
||||||
|
"exit_fullscreen_mode": "اخرج من وضع ملء الشاشة",
|
||||||
|
"loop": "حلقة",
|
||||||
|
"media_error_description": "خطأ في الوسائط تسبب في ايقاف التشغيل. قد تكون الوسائط تالفة او ان متصفحك لا يدعم الصيغة.",
|
||||||
|
"media_loading": "تحميل الوسائط",
|
||||||
|
"mute": "كتم",
|
||||||
|
"network_error": "خطأ في الشبكة",
|
||||||
|
"network_error_description": "خطأ في الشبكة تسبب في فشل تنزيل الوسائط.",
|
||||||
|
"not_supported_error": "المصدر غير مدعوم",
|
||||||
|
"playback_rate": "معدل التشغيل",
|
||||||
|
"playback_rate_current": "معدل التشغيل الحالي",
|
||||||
|
"playback_rate_value": "معدل التشغيل {playbackRate}",
|
||||||
|
"playback_time": "وقت التشغيل",
|
||||||
|
"quality": "جودة",
|
||||||
|
"second": "ثانية",
|
||||||
|
"seconds": "ثواني",
|
||||||
|
"time_value_of_total_time": "{currentTime} من {totalTime}",
|
||||||
|
"time_value_remaining": "{time} متبقي",
|
||||||
|
"unmute": "الغاء الكتم",
|
||||||
|
"unsupported_error_description": "حدث خطأ غير مدعوم. حدث فشل في الخادم او الشبكة, او المتصفح الخاص بك لا يدعم هذه الصيغة.",
|
||||||
|
"video_not_loaded_unknown_time": "الفيديو غير محمل, الوقت غير معلوم.",
|
||||||
|
"video_player": "مشغل الفيديو",
|
||||||
|
"volume": "حجم"
|
||||||
|
},
|
||||||
"media_type": "نوع الوسائط",
|
"media_type": "نوع الوسائط",
|
||||||
"memories": "الذكريات",
|
"memories": "الذكريات",
|
||||||
"memories_all_caught_up": "كل شيء محدث",
|
"memories_all_caught_up": "كل شيء محدث",
|
||||||
@@ -1543,9 +1606,10 @@
|
|||||||
"mobile_app": "تطبيق الجوال",
|
"mobile_app": "تطبيق الجوال",
|
||||||
"mobile_app_download_onboarding_note": "قم بتنزيل التطبيق المصاحب للهاتف المحمول باستخدام الخيارات التالية",
|
"mobile_app_download_onboarding_note": "قم بتنزيل التطبيق المصاحب للهاتف المحمول باستخدام الخيارات التالية",
|
||||||
"model": "نموذج",
|
"model": "نموذج",
|
||||||
|
"modify_date": "تغيير التاريخ",
|
||||||
"month": "شهر",
|
"month": "شهر",
|
||||||
"monthly_title_text_date_format": "ط ط ط",
|
|
||||||
"more": "المزيد",
|
"more": "المزيد",
|
||||||
|
"motion": "حركة",
|
||||||
"move": "تحريك",
|
"move": "تحريك",
|
||||||
"move_down": "انزل الى الاسفل",
|
"move_down": "انزل الى الاسفل",
|
||||||
"move_off_locked_folder": "تحريك خارج المجلد المقفل",
|
"move_off_locked_folder": "تحريك خارج المجلد المقفل",
|
||||||
@@ -1562,6 +1626,8 @@
|
|||||||
"multiselect_grid_edit_gps_err_read_only": "لا يمكن تعديل موقع الأصول (المواد) للقراءة فقط، سوف يتخطى",
|
"multiselect_grid_edit_gps_err_read_only": "لا يمكن تعديل موقع الأصول (المواد) للقراءة فقط، سوف يتخطى",
|
||||||
"mute_memories": "كتم الذكريات",
|
"mute_memories": "كتم الذكريات",
|
||||||
"my_albums": "ألبوماتي",
|
"my_albums": "ألبوماتي",
|
||||||
|
"my_immich_description": "نسخ الصفحة الحالية كرابط Immich الخاص بي",
|
||||||
|
"my_immich_title": "رابط Immich الخاص بي",
|
||||||
"name": "الاسم",
|
"name": "الاسم",
|
||||||
"name_or_nickname": "الاسم أو اللقب",
|
"name_or_nickname": "الاسم أو اللقب",
|
||||||
"name_required": "الاسم مطلوب",
|
"name_required": "الاسم مطلوب",
|
||||||
@@ -1589,7 +1655,6 @@
|
|||||||
"next": "التالي",
|
"next": "التالي",
|
||||||
"next_memory": "الذكرى التالية",
|
"next_memory": "الذكرى التالية",
|
||||||
"no": "لا",
|
"no": "لا",
|
||||||
"no_actions_added": "لم تتم إضافة إجراءات حتى الان",
|
|
||||||
"no_albums_found": "لم يتم ايجاد البومات",
|
"no_albums_found": "لم يتم ايجاد البومات",
|
||||||
"no_albums_message": "قم بإنشاء ألبوم لتنظيم الصور ومقاطع الفيديو الخاصة بك",
|
"no_albums_message": "قم بإنشاء ألبوم لتنظيم الصور ومقاطع الفيديو الخاصة بك",
|
||||||
"no_albums_with_name_yet": "يبدو أنه ليس لديك أي ألبومات بهذا الاسم حتى الآن.",
|
"no_albums_with_name_yet": "يبدو أنه ليس لديك أي ألبومات بهذا الاسم حتى الآن.",
|
||||||
@@ -1606,7 +1671,6 @@
|
|||||||
"no_exif_info_available": "لا تتوفر معلومات exif",
|
"no_exif_info_available": "لا تتوفر معلومات exif",
|
||||||
"no_explore_results_message": "قم برفع المزيد من الصور لاستكشاف مجموعتك.",
|
"no_explore_results_message": "قم برفع المزيد من الصور لاستكشاف مجموعتك.",
|
||||||
"no_favorites_message": "أضف المفضلة للعثور بسرعة على أفضل الصور ومقاطع الفيديو",
|
"no_favorites_message": "أضف المفضلة للعثور بسرعة على أفضل الصور ومقاطع الفيديو",
|
||||||
"no_filters_added": "لم تتم إضافة أي فلتر بعد",
|
|
||||||
"no_libraries_message": "إنشاء مكتبة خارجية لعرض الصور ومقاطع الفيديو الخاصة بك",
|
"no_libraries_message": "إنشاء مكتبة خارجية لعرض الصور ومقاطع الفيديو الخاصة بك",
|
||||||
"no_local_assets_found": "لم يتم العثور على أي اصول محلية تتطابق مع قيمة التحقق هذه",
|
"no_local_assets_found": "لم يتم العثور على أي اصول محلية تتطابق مع قيمة التحقق هذه",
|
||||||
"no_location_set": "لم يتم تحديد موقع",
|
"no_location_set": "لم يتم تحديد موقع",
|
||||||
@@ -1619,6 +1683,7 @@
|
|||||||
"no_results": "لا يوجد نتائج",
|
"no_results": "لا يوجد نتائج",
|
||||||
"no_results_description": "جرب كلمة رئيسية مرادفة أو أكثر عمومية",
|
"no_results_description": "جرب كلمة رئيسية مرادفة أو أكثر عمومية",
|
||||||
"no_shared_albums_message": "قم بإنشاء ألبوم لمشاركة الصور ومقاطع الفيديو مع الأشخاص في شبكتك",
|
"no_shared_albums_message": "قم بإنشاء ألبوم لمشاركة الصور ومقاطع الفيديو مع الأشخاص في شبكتك",
|
||||||
|
"no_steps": "لم يتم اضافة خطوات بعد",
|
||||||
"no_uploads_in_progress": "لا يوجد اي ملفات قيد الرفع",
|
"no_uploads_in_progress": "لا يوجد اي ملفات قيد الرفع",
|
||||||
"none": "لا يوجد",
|
"none": "لا يوجد",
|
||||||
"not_allowed": "غير مسموح",
|
"not_allowed": "غير مسموح",
|
||||||
@@ -1664,6 +1729,7 @@
|
|||||||
"organize_into_albums": "ترتيب في ألبومات",
|
"organize_into_albums": "ترتيب في ألبومات",
|
||||||
"organize_into_albums_description": "أضف الصور الموجودة إلى الألبومات باستخدام إعدادات النسخ المتزامن الحالية",
|
"organize_into_albums_description": "أضف الصور الموجودة إلى الألبومات باستخدام إعدادات النسخ المتزامن الحالية",
|
||||||
"organize_your_library": "تنظيم مكتبتك",
|
"organize_your_library": "تنظيم مكتبتك",
|
||||||
|
"orientation": "اتجاه",
|
||||||
"original": "أصلي",
|
"original": "أصلي",
|
||||||
"other": "أخرى",
|
"other": "أخرى",
|
||||||
"other_devices": "أجهزة أخرى",
|
"other_devices": "أجهزة أخرى",
|
||||||
@@ -1755,6 +1821,8 @@
|
|||||||
"play_original_video_setting_description": "تفضيل تشغيل مقاطع الفيديو الأصلية بدلاً من مقاطع الفيديو المحولة. إذا لم يكن الملف الأصلي متوافقًا، فقد لا يتم تشغيله بشكل صحيح.",
|
"play_original_video_setting_description": "تفضيل تشغيل مقاطع الفيديو الأصلية بدلاً من مقاطع الفيديو المحولة. إذا لم يكن الملف الأصلي متوافقًا، فقد لا يتم تشغيله بشكل صحيح.",
|
||||||
"play_transcoded_video": "تشغيل الفيديو المُعاد ترميزه",
|
"play_transcoded_video": "تشغيل الفيديو المُعاد ترميزه",
|
||||||
"please_auth_to_access": "الرجاء القيام بالمصادقة للوصول",
|
"please_auth_to_access": "الرجاء القيام بالمصادقة للوصول",
|
||||||
|
"plugin_method_filter_type": "تصفية",
|
||||||
|
"plugin_method_filter_type_description": "هذه الوسيلة تستطيع تصفية الاحداث و تمنع تشغيل الخطوات التالية شرطيا",
|
||||||
"port": "المنفذ",
|
"port": "المنفذ",
|
||||||
"preferences_settings_subtitle": "ادارة تفضيلات التطبيق",
|
"preferences_settings_subtitle": "ادارة تفضيلات التطبيق",
|
||||||
"preferences_settings_title": "التفضيلات",
|
"preferences_settings_title": "التفضيلات",
|
||||||
@@ -1776,6 +1844,7 @@
|
|||||||
"profile_drawer_readonly_mode": "تم تفعيل وضع القراءة فقط. اضغط مطولا على رمز صورة المستخدم للخروج.",
|
"profile_drawer_readonly_mode": "تم تفعيل وضع القراءة فقط. اضغط مطولا على رمز صورة المستخدم للخروج.",
|
||||||
"profile_image_of_user": "صورة الملف الشخصي لـ {user}",
|
"profile_image_of_user": "صورة الملف الشخصي لـ {user}",
|
||||||
"profile_picture_set": "مجموعة الصور الشخصية.",
|
"profile_picture_set": "مجموعة الصور الشخصية.",
|
||||||
|
"projection_type": "نوع العرض",
|
||||||
"public_album": "الألبوم العام",
|
"public_album": "الألبوم العام",
|
||||||
"public_share": "مشاركة عامة",
|
"public_share": "مشاركة عامة",
|
||||||
"purchase_account_info": "داعم",
|
"purchase_account_info": "داعم",
|
||||||
@@ -1853,6 +1922,7 @@
|
|||||||
"remove_assets_title": "هل تريد إزالة المحتويات؟",
|
"remove_assets_title": "هل تريد إزالة المحتويات؟",
|
||||||
"remove_custom_date_range": "إزالة النطاق الزمني المخصص",
|
"remove_custom_date_range": "إزالة النطاق الزمني المخصص",
|
||||||
"remove_deleted_assets": "إزالة الملفات الغير متصلة",
|
"remove_deleted_assets": "إزالة الملفات الغير متصلة",
|
||||||
|
"remove_filter": "ازالة التصفية",
|
||||||
"remove_from_album": "إزالة من الألبوم",
|
"remove_from_album": "إزالة من الألبوم",
|
||||||
"remove_from_album_action_prompt": "تم ازالة {count} من الالبوم",
|
"remove_from_album_action_prompt": "تم ازالة {count} من الالبوم",
|
||||||
"remove_from_favorites": "إزالة من المفضلة",
|
"remove_from_favorites": "إزالة من المفضلة",
|
||||||
@@ -1926,6 +1996,8 @@
|
|||||||
"scan_settings": "إعدادات الفحص",
|
"scan_settings": "إعدادات الفحص",
|
||||||
"scanning": "جاري البحث",
|
"scanning": "جاري البحث",
|
||||||
"scanning_for_album": "جارٍ الفحص عن ألبوم...",
|
"scanning_for_album": "جارٍ الفحص عن ألبوم...",
|
||||||
|
"screencast_mode_description": "إظهار مؤشرات أحداث لوحة المفاتيح والماوس على الشاشة",
|
||||||
|
"screencast_mode_title": "تبديل وضع تسجيل الشاشة",
|
||||||
"search": "البحث",
|
"search": "البحث",
|
||||||
"search_albums": "البحث في الألبومات",
|
"search_albums": "البحث في الألبومات",
|
||||||
"search_by_context": "البحث حسب السياق",
|
"search_by_context": "البحث حسب السياق",
|
||||||
@@ -1933,6 +2005,8 @@
|
|||||||
"search_by_description_example": "يوم المشي لمسافات طويلة في سابا",
|
"search_by_description_example": "يوم المشي لمسافات طويلة في سابا",
|
||||||
"search_by_filename": "البحث بإسم الملف أو نوعه",
|
"search_by_filename": "البحث بإسم الملف أو نوعه",
|
||||||
"search_by_filename_example": "كـ IMG_1234.JPG أو PNG",
|
"search_by_filename_example": "كـ IMG_1234.JPG أو PNG",
|
||||||
|
"search_by_full_path": "بحث بالمسار الكامل او المجلد",
|
||||||
|
"search_by_full_path_example": "/احمد/مشاريع/طباعة_ثلاثية_الابعاد/2026-07-01 - يمكنك البحث عن مشاريع, طباعة_ثلاثية_الابعاد, 2026 الخ.",
|
||||||
"search_by_ocr": "البحث عن طريق التعرف البصري على الحروف",
|
"search_by_ocr": "البحث عن طريق التعرف البصري على الحروف",
|
||||||
"search_by_ocr_example": "لاتيه",
|
"search_by_ocr_example": "لاتيه",
|
||||||
"search_camera_lens_model": "بحث نموذج العدسة...",
|
"search_camera_lens_model": "بحث نموذج العدسة...",
|
||||||
@@ -2140,7 +2214,9 @@
|
|||||||
"show_in_timeline": "إظهار في المخطط الزمني",
|
"show_in_timeline": "إظهار في المخطط الزمني",
|
||||||
"show_in_timeline_setting_description": "إظهار الصور ومقاطع الفيديو من هذا المستخدم في المخطط الزمني الخاص بك",
|
"show_in_timeline_setting_description": "إظهار الصور ومقاطع الفيديو من هذا المستخدم في المخطط الزمني الخاص بك",
|
||||||
"show_keyboard_shortcuts": "إظهار اختصارات لوحة المفاتيح",
|
"show_keyboard_shortcuts": "إظهار اختصارات لوحة المفاتيح",
|
||||||
|
"show_less": "اضهر اقل",
|
||||||
"show_metadata": "إظهار البيانات الوصفية",
|
"show_metadata": "إظهار البيانات الوصفية",
|
||||||
|
"show_more_fields": "{count, plural, one {اضهر #حقل اكثر} other {اضهر # حقول اكثر}}",
|
||||||
"show_or_hide_info": "إظهار أو إخفاء المعلومات",
|
"show_or_hide_info": "إظهار أو إخفاء المعلومات",
|
||||||
"show_password": "إظهار كلمة المرور",
|
"show_password": "إظهار كلمة المرور",
|
||||||
"show_person_options": "إظهار خيارات الشخص",
|
"show_person_options": "إظهار خيارات الشخص",
|
||||||
@@ -2148,6 +2224,7 @@
|
|||||||
"show_schema": "أظهر المخطط",
|
"show_schema": "أظهر المخطط",
|
||||||
"show_search_options": "إظهار خيارات البحث",
|
"show_search_options": "إظهار خيارات البحث",
|
||||||
"show_shared_links": "عرض الروابط المشتركة",
|
"show_shared_links": "عرض الروابط المشتركة",
|
||||||
|
"show_slideshow_metadata_overlay": "عرض معلومات الصورة",
|
||||||
"show_slideshow_transition": "إظهار انتقال عرض الشرائح",
|
"show_slideshow_transition": "إظهار انتقال عرض الشرائح",
|
||||||
"show_supporter_badge": "شارة المؤيد",
|
"show_supporter_badge": "شارة المؤيد",
|
||||||
"show_supporter_badge_description": "إظهار شارة المؤيد",
|
"show_supporter_badge_description": "إظهار شارة المؤيد",
|
||||||
@@ -2163,9 +2240,13 @@
|
|||||||
"skip_to_folders": "تخطي إلى المجلدات",
|
"skip_to_folders": "تخطي إلى المجلدات",
|
||||||
"skip_to_tags": "تخطي إلى العلامات",
|
"skip_to_tags": "تخطي إلى العلامات",
|
||||||
"slideshow": "عرض الشرائح",
|
"slideshow": "عرض الشرائح",
|
||||||
|
"slideshow_metadata_overlay_mode": "محتوى التراكب",
|
||||||
|
"slideshow_metadata_overlay_mode_description_only": "وصف فقط",
|
||||||
|
"slideshow_metadata_overlay_mode_full": "كامل",
|
||||||
"slideshow_repeat": "اعادة عرض الشرائح",
|
"slideshow_repeat": "اعادة عرض الشرائح",
|
||||||
"slideshow_repeat_description": "العودة إلى البداية عند انتهاء عرض الشرائح",
|
"slideshow_repeat_description": "العودة إلى البداية عند انتهاء عرض الشرائح",
|
||||||
"slideshow_settings": "إعدادات عرض الشرائح",
|
"slideshow_settings": "إعدادات عرض الشرائح",
|
||||||
|
"smart_album": "ألبوم ذكي",
|
||||||
"sort_albums_by": "رتب الألبومات حسب...",
|
"sort_albums_by": "رتب الألبومات حسب...",
|
||||||
"sort_created": "تاريخ الإنشاء",
|
"sort_created": "تاريخ الإنشاء",
|
||||||
"sort_items": "عدد العناصر",
|
"sort_items": "عدد العناصر",
|
||||||
@@ -2188,6 +2269,11 @@
|
|||||||
"start_date_before_end_date": "يجب أن يكون تاريخ بدء الفترة قبل تاريخ نهايتها",
|
"start_date_before_end_date": "يجب أن يكون تاريخ بدء الفترة قبل تاريخ نهايتها",
|
||||||
"state": "الولاية",
|
"state": "الولاية",
|
||||||
"status": "الحالة",
|
"status": "الحالة",
|
||||||
|
"step_delete": "حذف خطوة",
|
||||||
|
"step_delete_confirm": "هل انت متاكد من انك تريد حذف هذه الخطوة؟",
|
||||||
|
"step_details": "تفاصيل الخطوة",
|
||||||
|
"steps": "خطوات",
|
||||||
|
"steps_count": "{count, plural, one {# خطوة} other {# خطوات}}",
|
||||||
"stop_casting": "ايقاف البث",
|
"stop_casting": "ايقاف البث",
|
||||||
"stop_motion_photo": "إيقاف حركة الصورة",
|
"stop_motion_photo": "إيقاف حركة الصورة",
|
||||||
"stop_photo_sharing": "توقف عن مشاركة صورك؟",
|
"stop_photo_sharing": "توقف عن مشاركة صورك؟",
|
||||||
@@ -2214,6 +2300,8 @@
|
|||||||
"sync_status": "حالة النسخ المتزامن",
|
"sync_status": "حالة النسخ المتزامن",
|
||||||
"sync_status_subtitle": "عرض وإدارة نظام النسخ المتزامن",
|
"sync_status_subtitle": "عرض وإدارة نظام النسخ المتزامن",
|
||||||
"sync_upload_album_setting_subtitle": "انشئ و ارفع صورك و فديوهاتك الالبومات المختارة في Immich",
|
"sync_upload_album_setting_subtitle": "انشئ و ارفع صورك و فديوهاتك الالبومات المختارة في Immich",
|
||||||
|
"system_theme": "سمة النظام",
|
||||||
|
"system_theme_command_description": "استعمل سمة النظام ({value})",
|
||||||
"tag": "العلامة",
|
"tag": "العلامة",
|
||||||
"tag_assets": "أصول العلامة",
|
"tag_assets": "أصول العلامة",
|
||||||
"tag_created": "تم إنشاء العلامة: {tag}",
|
"tag_created": "تم إنشاء العلامة: {tag}",
|
||||||
@@ -2279,7 +2367,7 @@
|
|||||||
"trash_page_title": "سلة المهملات ({count})",
|
"trash_page_title": "سلة المهملات ({count})",
|
||||||
"trashed_items_will_be_permanently_deleted_after": "سيتم حذفُ العناصر المحذوفة نِهائيًا بعد {days, plural, one {# يوم} other {# أيام }}.",
|
"trashed_items_will_be_permanently_deleted_after": "سيتم حذفُ العناصر المحذوفة نِهائيًا بعد {days, plural, one {# يوم} other {# أيام }}.",
|
||||||
"trigger": "مفعِل",
|
"trigger": "مفعِل",
|
||||||
"trigger_asset_uploaded": "تم رفع الاصل",
|
"trigger_asset_uploaded": "رفع الاصل",
|
||||||
"trigger_asset_uploaded_description": "يتم تفعيله عند تحميل أصل جديد",
|
"trigger_asset_uploaded_description": "يتم تفعيله عند تحميل أصل جديد",
|
||||||
"trigger_description": "حدث يبدأ سير العمل",
|
"trigger_description": "حدث يبدأ سير العمل",
|
||||||
"trigger_person_recognized": "تم التعرف على شخص",
|
"trigger_person_recognized": "تم التعرف على شخص",
|
||||||
@@ -2319,7 +2407,6 @@
|
|||||||
"unsupported_field_type": "نوع حقل غير مدعوم",
|
"unsupported_field_type": "نوع حقل غير مدعوم",
|
||||||
"unsupported_file_type": "لا يمكن رفع الملف {file} لأن نوع الملف {type} غير مدعوم.",
|
"unsupported_file_type": "لا يمكن رفع الملف {file} لأن نوع الملف {type} غير مدعوم.",
|
||||||
"untagged": "غير مُعَلَّم",
|
"untagged": "غير مُعَلَّم",
|
||||||
"untitled_workflow": "خطة سير عمل بدون عنوان",
|
|
||||||
"up_next": "التالي",
|
"up_next": "التالي",
|
||||||
"update_location_action_prompt": "تحديث موقع {count} عناصر محددة على النحو التالي:",
|
"update_location_action_prompt": "تحديث موقع {count} عناصر محددة على النحو التالي:",
|
||||||
"updated_at": "تم التحديث",
|
"updated_at": "تم التحديث",
|
||||||
@@ -2348,6 +2435,7 @@
|
|||||||
"use_browser_locale_description": "تنسيق التواريخ والأوقات والأرقام وفقًا لإعدادات اللغة في متصفحك",
|
"use_browser_locale_description": "تنسيق التواريخ والأوقات والأرقام وفقًا لإعدادات اللغة في متصفحك",
|
||||||
"use_current_connection": "استخدم الاتصال الحالي",
|
"use_current_connection": "استخدم الاتصال الحالي",
|
||||||
"use_custom_date_range": "استخدم النطاق الزمني المخصص بدلاً من ذلك",
|
"use_custom_date_range": "استخدم النطاق الزمني المخصص بدلاً من ذلك",
|
||||||
|
"use_template": "استخدام القالب",
|
||||||
"user": "مستخدم",
|
"user": "مستخدم",
|
||||||
"user_has_been_deleted": "هذا المستخدم تم حذفه.",
|
"user_has_been_deleted": "هذا المستخدم تم حذفه.",
|
||||||
"user_id": "معرف المستخدم",
|
"user_id": "معرف المستخدم",
|
||||||
@@ -2377,6 +2465,7 @@
|
|||||||
"video": "فيديو",
|
"video": "فيديو",
|
||||||
"video_hover_setting": "تشغيل الصورة المصغرة للفيديو عند التمرير",
|
"video_hover_setting": "تشغيل الصورة المصغرة للفيديو عند التمرير",
|
||||||
"video_hover_setting_description": "تشغيل الصورة المصغرة للفيديو عند تحريك الماوس فوق العنصر. حتى عند التعطيل، يمكن بدء التشغيل عن طريق التمرير فوق رمز التشغيل.",
|
"video_hover_setting_description": "تشغيل الصورة المصغرة للفيديو عند تحريك الماوس فوق العنصر. حتى عند التعطيل، يمكن بدء التشغيل عن طريق التمرير فوق رمز التشغيل.",
|
||||||
|
"video_quality": "جودة الفيديو",
|
||||||
"videos": "فيديوهات",
|
"videos": "فيديوهات",
|
||||||
"videos_count": "{count, plural, one {# مقطع فيديو } other {# مقاطع الفيديو }}",
|
"videos_count": "{count, plural, one {# مقطع فيديو } other {# مقاطع الفيديو }}",
|
||||||
"videos_only": "الفديوات فقط",
|
"videos_only": "الفديوات فقط",
|
||||||
@@ -2409,8 +2498,10 @@
|
|||||||
"week": "أسبوع",
|
"week": "أسبوع",
|
||||||
"welcome": "مرحباً",
|
"welcome": "مرحباً",
|
||||||
"welcome_to_immich": "مرحباً بك في Immich",
|
"welcome_to_immich": "مرحباً بك في Immich",
|
||||||
|
"when": "عندما",
|
||||||
"width": "عُرض",
|
"width": "عُرض",
|
||||||
"wifi_name": "اسم شبكة Wi-Fi",
|
"wifi_name": "اسم شبكة Wi-Fi",
|
||||||
|
"workflow": "سير العمل",
|
||||||
"workflow_delete_prompt": "متأكد من حذف سير العمل هذا؟",
|
"workflow_delete_prompt": "متأكد من حذف سير العمل هذا؟",
|
||||||
"workflow_deleted": "تم حذف سير العمل",
|
"workflow_deleted": "تم حذف سير العمل",
|
||||||
"workflow_description": "وصف سير العمل",
|
"workflow_description": "وصف سير العمل",
|
||||||
@@ -2420,11 +2511,13 @@
|
|||||||
"workflow_name": "اسم سير العمل",
|
"workflow_name": "اسم سير العمل",
|
||||||
"workflow_navigation_prompt": "متاكد من المغادرة بدون حفظ التغييرات؟",
|
"workflow_navigation_prompt": "متاكد من المغادرة بدون حفظ التغييرات؟",
|
||||||
"workflow_summary": "ملخص سير العمل",
|
"workflow_summary": "ملخص سير العمل",
|
||||||
|
"workflow_templates": "قوالب سير العمل",
|
||||||
"workflow_update_success": "تم تحديث سير العمل بنجاح",
|
"workflow_update_success": "تم تحديث سير العمل بنجاح",
|
||||||
"workflow_updated": "تم تحديث سير العمل",
|
"workflow_updated": "تم تحديث سير العمل",
|
||||||
"workflows": "سير العمل",
|
"workflows": "سير العمليات",
|
||||||
"workflows_help_text": "تعمل سير العمل على أتمتة الإجراءات على أصولك بناءً على المفعلات والفلاتر",
|
"workflows_help_text": "تعمل سير العمل على أتمتة الإجراءات على أصولك بناءً على المفعلات والفلاتر",
|
||||||
"wrong_pin_code": "رمز التعريف الشخصي خاطئ",
|
"wrong_pin_code": "رمز التعريف الشخصي خاطئ",
|
||||||
|
"x_of_total": "{x}\\{total}",
|
||||||
"year": "سنة",
|
"year": "سنة",
|
||||||
"years_ago": "{years, plural, one {# سنة} other {# سنوات}} مضت",
|
"years_ago": "{years, plural, one {# سنة} other {# سنوات}} مضت",
|
||||||
"yes": "نعم",
|
"yes": "نعم",
|
||||||
|
|||||||
+24
-2
@@ -5,6 +5,7 @@
|
|||||||
"acknowledge": "Təsdiq et",
|
"acknowledge": "Təsdiq et",
|
||||||
"action": "Əməliyyat",
|
"action": "Əməliyyat",
|
||||||
"action_common_update": "Yenilə",
|
"action_common_update": "Yenilə",
|
||||||
|
"action_description": "Filtrlənmiş aktivliklər üzərində yerinə yetiriləcək əməliyyatlar toplusu",
|
||||||
"actions": "Əməliyyatlar",
|
"actions": "Əməliyyatlar",
|
||||||
"active": "Aktiv",
|
"active": "Aktiv",
|
||||||
"active_count": "Aktiv: {count}",
|
"active_count": "Aktiv: {count}",
|
||||||
@@ -15,6 +16,9 @@
|
|||||||
"add_a_location": "Məkan əlavə et",
|
"add_a_location": "Məkan əlavə et",
|
||||||
"add_a_name": "Ad əlavə et",
|
"add_a_name": "Ad əlavə et",
|
||||||
"add_a_title": "Başlıq əlavə et",
|
"add_a_title": "Başlıq əlavə et",
|
||||||
|
"add_action": "Yeni əməliyyat əlavə et",
|
||||||
|
"add_action_description": "Əməliyyat əlavə etmək üçün klikləyin",
|
||||||
|
"add_assets": "Aktivlik əlavə et",
|
||||||
"add_birthday": "Doğum günü əlavə et",
|
"add_birthday": "Doğum günü əlavə et",
|
||||||
"add_endpoint": "Son nöqtə əlavə et",
|
"add_endpoint": "Son nöqtə əlavə et",
|
||||||
"add_exclusion_pattern": "Çıxarma nümunəsi əlavə et",
|
"add_exclusion_pattern": "Çıxarma nümunəsi əlavə et",
|
||||||
@@ -46,7 +50,7 @@
|
|||||||
"authentication_settings": "Səlahiyyətləndirmə parametrləri",
|
"authentication_settings": "Səlahiyyətləndirmə parametrləri",
|
||||||
"authentication_settings_description": "Şifrə, OAuth və digər səlahiyyətləndirmə parametrləri",
|
"authentication_settings_description": "Şifrə, OAuth və digər səlahiyyətləndirmə parametrləri",
|
||||||
"authentication_settings_disable_all": "Bütün giriş etmə metodlarını söndürmək istədiyinizdən əminsinizmi? Giriş etmə funksiyası tamamilə söndürüləcəkdir.",
|
"authentication_settings_disable_all": "Bütün giriş etmə metodlarını söndürmək istədiyinizdən əminsinizmi? Giriş etmə funksiyası tamamilə söndürüləcəkdir.",
|
||||||
"authentication_settings_reenable": "Yenidən aktiv etmək üçün <link> Server Əmri</link> -ni istifadə edin.",
|
"authentication_settings_reenable": "Yenidən aktiv etmək üçün <link> Server Əmri</link>-ni istifadə edin.",
|
||||||
"background_task_job": "Arxa plan tapşırıqları",
|
"background_task_job": "Arxa plan tapşırıqları",
|
||||||
"backup_database": "Verilənlər bazasının dump-ını yaradın",
|
"backup_database": "Verilənlər bazasının dump-ını yaradın",
|
||||||
"backup_database_enable_description": "Verilənlər bazasının artıq nüsxələrini aktiv et",
|
"backup_database_enable_description": "Verilənlər bazasının artıq nüsxələrini aktiv et",
|
||||||
@@ -54,6 +58,7 @@
|
|||||||
"backup_onboarding_1_description": "buludda və ya başqa fiziki yerdə saytdan kənar surət.",
|
"backup_onboarding_1_description": "buludda və ya başqa fiziki yerdə saytdan kənar surət.",
|
||||||
"backup_onboarding_2_description": "müxtəlif cihazlarda yerli nüsxələr. Bura əsas fayllar və həmin faylların ehtiyat lokal nüsxəsi daxildir.",
|
"backup_onboarding_2_description": "müxtəlif cihazlarda yerli nüsxələr. Bura əsas fayllar və həmin faylların ehtiyat lokal nüsxəsi daxildir.",
|
||||||
"backup_onboarding_3_description": "orijinal fayllar da daxil olmaqla məlumatlarınızın ümumi surətləri. Buraya 1 kənar nüsxə və 2 lokal nüsxə daxildir.",
|
"backup_onboarding_3_description": "orijinal fayllar da daxil olmaqla məlumatlarınızın ümumi surətləri. Buraya 1 kənar nüsxə və 2 lokal nüsxə daxildir.",
|
||||||
|
"backup_onboarding_description": "<backblaze-link>3-2-1 yedəkləmə strategiyası</backblaze-link> məlumatlarınızı qorumaq üçün tövsiyə olunur. Yüklədiyiniz şəkil və videoların, həmçinin Immich verilənlər bazasının surətlərini saxlamalısınız ki, hərtərəfli yedəkləmə həlli əldə edəsiniz.",
|
||||||
"backup_onboarding_footer": "Immich-in ehtiyat nüsxəsini çıxarmaq haqqında ətraflı məlumat üçün <link>sənədlərə</link> müraciət edin.",
|
"backup_onboarding_footer": "Immich-in ehtiyat nüsxəsini çıxarmaq haqqında ətraflı məlumat üçün <link>sənədlərə</link> müraciət edin.",
|
||||||
"backup_onboarding_parts_title": "3-2-1 ehtiyat nüsxəsinə aşağıdakılar daxildir:",
|
"backup_onboarding_parts_title": "3-2-1 ehtiyat nüsxəsinə aşağıdakılar daxildir:",
|
||||||
"backup_onboarding_title": "Ehtiyat surətlər",
|
"backup_onboarding_title": "Ehtiyat surətlər",
|
||||||
@@ -61,14 +66,31 @@
|
|||||||
"backup_settings_description": "Verilənlər bazasının ehtiyat nüsxə parametrlərini idarə et",
|
"backup_settings_description": "Verilənlər bazasının ehtiyat nüsxə parametrlərini idarə et",
|
||||||
"cleared_jobs": "{job} üçün tapşırıqlar silindi",
|
"cleared_jobs": "{job} üçün tapşırıqlar silindi",
|
||||||
"config_set_by_file": "Konfiqurasiya hal-hazırda konfiqurasiya faylı ilə təyin olunub",
|
"config_set_by_file": "Konfiqurasiya hal-hazırda konfiqurasiya faylı ilə təyin olunub",
|
||||||
"confirm_delete_library": "{library} kitabxanasını silmək istədiyinizdən əminmisiniz?",
|
"confirm_delete_library": "{library} kitabxanasını silmək istədiyinizə əminmisiniz?",
|
||||||
"confirm_email_below": "Təsdiqləmək üçün aşağıya {email} yazın",
|
"confirm_email_below": "Təsdiqləmək üçün aşağıya {email} yazın",
|
||||||
|
"confirm_reprocess_all_faces": "Bütün üzləri yenidən emal etmək istədiyinizə əminsiniz? Bu, həmçinin adlandırılmış şəxsləri siləcək.",
|
||||||
"confirm_user_password_reset": "{user} adlı istifadəçinin şifrəsini sıfırlamaq istədiyinizdən əminmisiniz?",
|
"confirm_user_password_reset": "{user} adlı istifadəçinin şifrəsini sıfırlamaq istədiyinizdən əminmisiniz?",
|
||||||
|
"confirm_user_pin_code_reset": "{user} istifadəçisinin PIN kodunu sıfırlamaq istədiyinizə əminsiniz?",
|
||||||
|
"copy_config_to_clipboard_description": "Cari sistem konfiqurasiyasını JSON obyekt kimi mübadilə buferinə kopyalayın",
|
||||||
|
"create_job": "İş yarat",
|
||||||
|
"cron_expression": "Cron ifadəsi",
|
||||||
|
"cron_expression_description": "Cron formatından istifadə edərək skan intervalını təyin edin. Ətraflı məlumat üçün nümunələrə baxa bilərsiniz. <link>Crontab Guru</link>",
|
||||||
|
"cron_expression_presets": "Cron ifadəsi ön ayarları",
|
||||||
"disable_login": "Giriş etməni söndür",
|
"disable_login": "Giriş etməni söndür",
|
||||||
"duplicate_detection_job_description": "Bənzər şəkilləri tapmaq üçün maşın öyrənməsini işə salın. Bu prosses Smart Search funksiyasına əsaslanır",
|
"duplicate_detection_job_description": "Bənzər şəkilləri tapmaq üçün maşın öyrənməsini işə salın. Bu prosses Smart Search funksiyasına əsaslanır",
|
||||||
|
"exclusion_pattern_description": "İstisna nümunələri kitabxananızı skan edərkən faylları və qovluqları nəzərə almamağa imkan verir. Bu, RAW faylları kimi idxal etmək istəmədiyiniz faylları olan qovluqlarınız olduqda faydalıdır.",
|
||||||
|
"export_config_as_json_description": "Cari sistem konfiqurasiyasını JSON faylı kimi endirin",
|
||||||
|
"external_libraries_page_description": "Admin xarici kitabxana səhifəsi",
|
||||||
"face_detection": "Üz tanıma",
|
"face_detection": "Üz tanıma",
|
||||||
|
"failed_job_command": "{command} əmri {job} işi üçün uğursuz oldu",
|
||||||
"force_delete_user_warning": "XƏBƏRDARLIQ: Bu əməliyyat istifadəçi və bütün məlumatları siləcəkdir. Bu prossesi və silinən faylları geri qaytarmaq olmaz.",
|
"force_delete_user_warning": "XƏBƏRDARLIQ: Bu əməliyyat istifadəçi və bütün məlumatları siləcəkdir. Bu prossesi və silinən faylları geri qaytarmaq olmaz.",
|
||||||
|
"image_format": "Format",
|
||||||
"image_format_description": "WebP, JPEG faylına görə daha kiçik həcmə sahibdir, lakin onu kodlaşdırmaq daha çox vaxt alır.",
|
"image_format_description": "WebP, JPEG faylına görə daha kiçik həcmə sahibdir, lakin onu kodlaşdırmaq daha çox vaxt alır.",
|
||||||
|
"image_fullsize_description": "Böyüdülmüş halda istifadə edilən, metadata-sı silinmiş tam ölçülü şəkil",
|
||||||
|
"image_fullsize_enabled": "Tam ölçülü şəkil generasiyasını aktiv et",
|
||||||
|
"image_fullsize_enabled_description": "Veb üçün uyğun olmayan formatlar üçün tam ölçülü şəkil yaradın. “Daxili önizləməyə üstünlük ver” aktiv olduqda, daxili önizləmələr çevrilmədən birbaşa istifadə olunur. JPEG kimi veb üçün uyğun formatlara təsir etmir.",
|
||||||
|
"image_fullsize_quality_description": "Tam ölçülü şəkil keyfiyyəti (1-100). Daha yüksək dəyər daha yaxşı keyfiyyət verir, lakin daha böyük ölçülü fayl yaradır.",
|
||||||
|
"image_fullsize_title": "Tam ölçülü şəkil tənzimləmələri",
|
||||||
"image_preview_title": "Önizləmə parametrləri",
|
"image_preview_title": "Önizləmə parametrləri",
|
||||||
"image_quality": "Keyfiyyət",
|
"image_quality": "Keyfiyyət",
|
||||||
"image_resolution": "Çözümlülük",
|
"image_resolution": "Çözümlülük",
|
||||||
|
|||||||
+1962
-39
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user