diff --git a/e2e/eslint.config.mjs b/e2e/eslint.config.mjs index 1e57910b0..2a0b02b56 100644 --- a/e2e/eslint.config.mjs +++ b/e2e/eslint.config.mjs @@ -55,6 +55,11 @@ export default typescriptEslint.config([ 'unicorn/prefer-event-target': 'off', 'unicorn/no-thenable': 'off', 'object-shorthand': ['error', 'always'], + 'unicorn/prefer-combined-guards': 'off', + 'unicorn/prefer-ternary': 'off', + 'unicorn/prefer-early-return': 'off', + 'unicorn/prefer-continue': 'off', + 'unicorn/no-immediate-mutation': 'off', }, }, ]); diff --git a/e2e/package.json b/e2e/package.json index 427d33806..062e82565 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -40,7 +40,7 @@ "eslint": "^10.0.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.1.3", - "eslint-plugin-unicorn": "^73.0.0", + "eslint-plugin-unicorn": "^75.0.0", "exiftool-vendored": "^35.0.0", "globals": "^17.0.0", "luxon": "^3.4.4", diff --git a/packages/cli/package.json b/packages/cli/package.json index 07bff16d3..59e60978c 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -33,7 +33,7 @@ "eslint": "^10.0.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.1.3", - "eslint-plugin-unicorn": "^73.0.0", + "eslint-plugin-unicorn": "^75.0.0", "globals": "^17.0.0", "mock-fs": "^5.2.0", "prettier": "^3.7.4", diff --git a/packages/cli/src/commands/asset.ts b/packages/cli/src/commands/asset.ts index 1850cc70b..6aab96dc3 100644 --- a/packages/cli/src/commands/asset.ts +++ b/packages/cli/src/commands/asset.ts @@ -199,10 +199,7 @@ export const checkForDuplicates = async (files: string[], { concurrency, skipHas format: '{message} | {bar} | {percentage}% | ETA: {eta_formatted} | {value}/{total}', formatValue: (v: number, options, type) => { // Don't format percentage - if (type === 'percentage') { - return v.toString(); - } - return byteSize(v).toString(); + return type === 'percentage' ? v.toString() : byteSize(v).toString(); }, etaBuffer: 100, // Increase samples for ETA calculation }, @@ -563,12 +560,15 @@ const updateAlbums = async (assets: Asset[], options: UploadOptionsDto) => { continue; } const albumId = existingAlbums.get(albumName); - if (albumId) { - if (!albumToAssets.has(albumId)) { - albumToAssets.set(albumId, []); - } - albumToAssets.get(albumId)?.push(asset.id); + + if (!albumId) { + continue; } + + if (!albumToAssets.has(albumId)) { + albumToAssets.set(albumId, []); + } + albumToAssets.get(albumId)?.push(asset.id); } const albumUpdateProgress = new SingleBar( diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts index b1a4d1b64..a49cede3b 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils.ts @@ -63,14 +63,16 @@ export const requirePermissions = async (permissions: Permission[]) => { } } - if (missing.length > 0) { - const combined = missing.map((permission) => `"${permission}"`).join(', '); - console.log( - `Missing required permission${s(missing.length)}: ${combined}. -Please make sure your API key has the correct permissions.`, - ); - process.exit(1); + if (missing.length === 0) { + return; } + + const combined = missing.map((permission) => `"${permission}"`).join(', '); + console.log( + `Missing required permission${s(missing.length)}: ${combined}. +Please make sure your API key has the correct permissions.`, + ); + process.exit(1); }; export const connect = async (url: string, key: string) => { @@ -120,10 +122,7 @@ export const readAuthFile = async (dir: string) => { // TODO add class-transform/validation const auth = yaml.parse(data.toString()) as AuthDto | OldAuthDto; const { instanceUrl, apiKey } = auth as OldAuthDto; - if (instanceUrl && apiKey) { - return { url: instanceUrl, key: apiKey }; - } - return auth as AuthDto; + return instanceUrl && apiKey ? { url: instanceUrl, key: apiKey } : (auth as AuthDto); } catch (error: Error | any) { if (error.code === 'ENOENT' || error.code === 'ENOTDIR') { console.log('No auth file exists. Please login first.'); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index deae2f1a7..eeb904b0f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -157,8 +157,8 @@ importers: specifier: ^5.1.3 version: 5.5.6(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.10.0(jiti@2.7.0)(supports-color@8.1.1)))(eslint@10.10.0(jiti@2.7.0)(supports-color@8.1.1))(prettier@3.9.8) eslint-plugin-unicorn: - specifier: ^73.0.0 - version: 73.0.0(eslint@10.10.0(jiti@2.7.0)(supports-color@8.1.1)) + specifier: ^75.0.0 + version: 75.0.0(eslint@10.10.0(jiti@2.7.0)(supports-color@8.1.1)) exiftool-vendored: specifier: ^35.0.0 version: 35.21.0 @@ -272,8 +272,8 @@ importers: specifier: ^5.1.3 version: 5.5.6(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.10.0(jiti@2.7.0)(supports-color@8.1.1)))(eslint@10.10.0(jiti@2.7.0)(supports-color@8.1.1))(prettier@3.9.8) eslint-plugin-unicorn: - specifier: ^73.0.0 - version: 73.0.0(eslint@10.10.0(jiti@2.7.0)(supports-color@8.1.1)) + specifier: ^75.0.0 + version: 75.0.0(eslint@10.10.0(jiti@2.7.0)(supports-color@8.1.1)) globals: specifier: ^17.0.0 version: 17.12.0 @@ -750,8 +750,8 @@ importers: specifier: ^5.1.3 version: 5.5.6(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.10.0(jiti@2.7.0)(supports-color@8.1.1)))(eslint@10.10.0(jiti@2.7.0)(supports-color@8.1.1))(prettier@3.9.8) eslint-plugin-unicorn: - specifier: ^73.0.0 - version: 73.0.0(eslint@10.10.0(jiti@2.7.0)(supports-color@8.1.1)) + specifier: ^75.0.0 + version: 75.0.0(eslint@10.10.0(jiti@2.7.0)(supports-color@8.1.1)) globals: specifier: ^17.0.0 version: 17.12.0 @@ -1009,8 +1009,8 @@ importers: specifier: ^3.12.4 version: 3.23.0(eslint@10.10.0(jiti@2.7.0)(supports-color@8.1.1))(svelte@5.57.0(@typescript-eslint/types@8.70.0)) eslint-plugin-unicorn: - specifier: ^73.0.0 - version: 73.0.0(eslint@10.10.0(jiti@2.7.0)(supports-color@8.1.1)) + specifier: ^75.0.0 + version: 75.0.0(eslint@10.10.0(jiti@2.7.0)(supports-color@8.1.1)) factory.ts: specifier: ^1.4.1 version: 1.4.2 @@ -6853,10 +6853,6 @@ packages: resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} engines: {node: '>=18'} - convert-hrtime@5.0.0: - resolution: {integrity: sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==} - engines: {node: '>=12'} - convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -6903,8 +6899,9 @@ packages: peerDependencies: webpack: ^5.1.0 - core-js-compat@3.49.0: - resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} + core-js-compat@3.50.0: + resolution: {integrity: sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q==} + engines: {node: '>=6.4.0'} core-js@3.49.0: resolution: {integrity: sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==} @@ -7618,6 +7615,10 @@ packages: resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} engines: {node: '>=0.12'} + entities@8.1.0: + resolution: {integrity: sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==} + engines: {node: '>=20.19.0'} + env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} @@ -7812,8 +7813,8 @@ packages: svelte: optional: true - eslint-plugin-unicorn@73.0.0: - resolution: {integrity: sha512-V0YatLe9nkGhXEXKe2Qljb1EY0sJHwDV0HUF1NKFwtsHh/fU7qGHDgv+6fchzZcgU2/7noHo2gdjnmo0P2uDPw==} + eslint-plugin-unicorn@75.0.0: + resolution: {integrity: sha512-72I7eSn+/z5CpgValFBl9mp2JE5UPD+nUUyB/VIGUMF1rCP1i37mA05zfIAVIU312A+qCZ38T8tSZr3oaMv4Mw==} engines: {node: '>=22'} peerDependencies: eslint: '>=10.4' @@ -8224,10 +8225,6 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - function-timeout@1.0.2: - resolution: {integrity: sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==} - engines: {node: '>=18'} - function.prototype.name@1.2.0: resolution: {integrity: sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==} engines: {node: '>= 0.4'} @@ -8894,10 +8891,6 @@ packages: is-hexadecimal@2.0.1: resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} - is-identifier@1.1.0: - resolution: {integrity: sha512-NhOds0mDx9lJu+1lBRO0xbwFo5nobA7GCk/0e5xjr6+6XugX985+0OyGX35BNrTkPAsdLcIKg02HUQJOK8D8kw==} - engines: {node: '>=18'} - is-in-ssh@1.0.0: resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} engines: {node: '>=20'} @@ -9610,10 +9603,6 @@ packages: magicast@0.5.3: resolution: {integrity: sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==} - make-asynchronous@1.1.0: - resolution: {integrity: sha512-ayF7iT+44LXdxJLTrTd3TLQpFDDvPCBxXxbv+pMUSuHA5Q8zyAfwkRP6aHHwNVFBUFWtxAHqwNJxF8vMZLAbVg==} - engines: {node: '>=18'} - make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} @@ -10414,10 +10403,6 @@ packages: resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} engines: {node: '>=12.20'} - p-event@6.0.1: - resolution: {integrity: sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==} - engines: {node: '>=16.17'} - p-finally@1.0.0: resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} engines: {node: '>=4'} @@ -10462,10 +10447,6 @@ packages: resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} engines: {node: '>=8'} - p-timeout@6.1.4: - resolution: {integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==} - engines: {node: '>=14.16'} - p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -12185,10 +12166,6 @@ packages: engines: {node: '>=16 || 14 >=14.17'} hasBin: true - super-regex@1.1.0: - resolution: {integrity: sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ==} - engines: {node: '>=18'} - superagent@10.3.0: resolution: {integrity: sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==} engines: {node: '>=14.18.0'} @@ -12487,10 +12464,6 @@ packages: thunky@1.1.0: resolution: {integrity: sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==} - time-span@5.1.0: - resolution: {integrity: sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==} - engines: {node: '>=12'} - timers-ext@0.1.8: resolution: {integrity: sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==} engines: {node: '>=0.12'} @@ -12655,10 +12628,6 @@ packages: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} - type-fest@4.41.0: - resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} - engines: {node: '>=16'} - type-fest@5.7.0: resolution: {integrity: sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==} engines: {node: '>=20'} @@ -13095,9 +13064,6 @@ packages: web-namespaces@2.0.1: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} - web-worker@1.5.0: - resolution: {integrity: sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==} - webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -14336,7 +14302,7 @@ snapshots: babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) babel-plugin-polyfill-corejs3: 0.14.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) - core-js-compat: 3.49.0 + core-js-compat: 3.50.0 semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -19432,7 +19398,7 @@ snapshots: dependencies: '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) - core-js-compat: 3.49.0 + core-js-compat: 3.50.0 transitivePeerDependencies: - supports-color @@ -19440,7 +19406,7 @@ snapshots: dependencies: '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) - core-js-compat: 3.49.0 + core-js-compat: 3.50.0 transitivePeerDependencies: - supports-color @@ -20039,8 +20005,6 @@ snapshots: content-type@2.0.0: {} - convert-hrtime@5.0.0: {} - convert-source-map@2.0.0: {} cookie-parser@1.4.7: @@ -20079,7 +20043,7 @@ snapshots: serialize-javascript: 6.0.2 webpack: 5.108.4(@swc/core@1.16.2(@swc/helpers@0.5.23))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.28))(csso@5.0.5)(esbuild@0.28.2)(html-minifier-terser@7.2.0)(lightningcss@1.33.0)(postcss@8.5.28)(uglify-js@3.19.3) - core-js-compat@3.49.0: + core-js-compat@3.50.0: dependencies: browserslist: 4.29.0 @@ -20853,6 +20817,8 @@ snapshots: entities@7.0.1: {} + entities@8.1.0: {} + env-paths@2.2.1: {} env-paths@3.0.0: {} @@ -21205,22 +21171,22 @@ snapshots: transitivePeerDependencies: - ts-node - eslint-plugin-unicorn@73.0.0(eslint@10.10.0(jiti@2.7.0)(supports-color@8.1.1)): + eslint-plugin-unicorn@75.0.0(eslint@10.10.0(jiti@2.7.0)(supports-color@8.1.1)): dependencies: '@eslint-community/eslint-utils': 4.10.1(eslint@10.10.0(jiti@2.7.0)(supports-color@8.1.1)) '@eslint/css-tree': 4.0.5 browserslist: 4.29.0 change-case: 5.4.4 ci-info: 4.4.0 - core-js-compat: 3.49.0 + core-js-compat: 3.50.0 detect-indent: 7.0.2 - entities: 4.5.0 + entities: 8.1.0 eslint: 10.10.0(jiti@2.7.0)(supports-color@8.1.1) find-up-simple: 1.0.1 globals: 17.12.0 + identifier-regex: 1.1.0 indent-string: 5.0.0 is-builtin-module: 5.0.0 - is-identifier: 1.1.0 pluralize: 8.0.0 quote-js-string: 0.1.0 regjsparser: 0.13.2 @@ -21759,8 +21725,6 @@ snapshots: function-bind@1.1.2: {} - function-timeout@1.0.2: {} - function.prototype.name@1.2.0: dependencies: call-bind: 1.0.9 @@ -22620,11 +22584,6 @@ snapshots: is-hexadecimal@2.0.1: {} - is-identifier@1.1.0: - dependencies: - identifier-regex: 1.1.0 - super-regex: 1.1.0 - is-in-ssh@1.0.0: {} is-inside-container@1.0.0: @@ -23283,12 +23242,6 @@ snapshots: '@babel/types': 7.29.7 source-map-js: 1.2.1 - make-asynchronous@1.1.0: - dependencies: - p-event: 6.0.1 - type-fest: 4.41.0 - web-worker: 1.5.0 - make-dir@3.1.0: dependencies: semver: 6.3.1 @@ -24399,10 +24352,6 @@ snapshots: p-cancelable@3.0.0: {} - p-event@6.0.1: - dependencies: - p-timeout: 6.1.4 - p-finally@1.0.0: {} p-limit@2.3.0: @@ -24448,8 +24397,6 @@ snapshots: dependencies: p-finally: 1.0.0 - p-timeout@6.1.4: {} - p-try@2.2.0: {} package-json-from-dist@1.0.1: {} @@ -26497,12 +26444,6 @@ snapshots: tinyglobby: 0.2.17 ts-interface-checker: 0.1.13 - super-regex@1.1.0: - dependencies: - function-timeout: 1.0.2 - make-asynchronous: 1.1.0 - time-span: 5.1.0 - superagent@10.3.0(supports-color@8.1.1): dependencies: component-emitter: 1.3.1 @@ -26890,10 +26831,6 @@ snapshots: thunky@1.1.0: {} - time-span@5.1.0: - dependencies: - convert-hrtime: 5.0.0 - timers-ext@0.1.8: dependencies: es5-ext: 0.10.64 @@ -27044,8 +26981,6 @@ snapshots: type-fest@2.19.0: {} - type-fest@4.41.0: {} - type-fest@5.7.0: dependencies: tagged-tag: 1.0.0 @@ -27551,8 +27486,6 @@ snapshots: web-namespaces@2.0.1: {} - web-worker@1.5.0: {} - webidl-conversions@3.0.1: {} webidl-conversions@7.0.0: diff --git a/server/eslint.config.mjs b/server/eslint.config.mjs index e65812a72..e28657845 100644 --- a/server/eslint.config.mjs +++ b/server/eslint.config.mjs @@ -64,6 +64,10 @@ export default defineConfig([ 'unicorn/no-computed-property-existence-check': 'off', 'unicorn/no-non-function-verb-prefix': 'off', 'unicorn/prefer-simple-condition-first': 'off', + 'unicorn/prefer-ternary': 'off', + 'unicorn/prefer-combined-guards': 'off', + 'unicorn/no-immediate-mutation': 'off', + 'unicorn/prefer-minimal-ternary': 'off', // prefer the typescript-eslint type-aware version 'unicorn/require-array-sort-compare': 'off', '@typescript-eslint/require-array-sort-compare': 'error', diff --git a/server/package.json b/server/package.json index 001bbe2b3..3c2334037 100644 --- a/server/package.json +++ b/server/package.json @@ -157,7 +157,7 @@ "eslint-import-resolver-typescript": "^4.4.5", "eslint-plugin-import-x": "^4.17.1", "eslint-plugin-prettier": "^5.1.3", - "eslint-plugin-unicorn": "^73.0.0", + "eslint-plugin-unicorn": "^75.0.0", "globals": "^17.0.0", "mock-fs": "^5.2.0", "pngjs": "^7.0.0", diff --git a/server/src/commands/media-location.command.ts b/server/src/commands/media-location.command.ts index 66bbf926f..8c39eb0b6 100644 --- a/server/src/commands/media-location.command.ts +++ b/server/src/commands/media-location.command.ts @@ -17,14 +17,16 @@ export class ChangeMediaLocationCommand extends CommandRunner { hint = hint ? ` (${hint})` : ''; const paths = await this.service.getSampleFilePaths(); - if (paths.length > 0) { - let message = ` Examples from the database${hint}:\n`; - for (const path of paths) { - message += ` - ${path}\n`; - } - - console.log(`\n${message}`); + if (paths.length === 0) { + return; } + + let message = ` Examples from the database${hint}:\n`; + for (const path of paths) { + message += ` - ${path}\n`; + } + + console.log(`\n${message}`); } async run(): Promise { diff --git a/server/src/decorators.ts b/server/src/decorators.ts index 6a832ad38..fc08cb40c 100644 --- a/server/src/decorators.ts +++ b/server/src/decorators.ts @@ -45,10 +45,11 @@ function chunks(collection: Array | Set, size: number): Array> let chunk = new Set(); for (const element of collection) { chunk.add(element); - if (chunk.size === size) { - result.push(chunk); - chunk = new Set(); + if (chunk.size !== size) { + continue; } + result.push(chunk); + chunk = new Set(); } if (chunk.size > 0) { result.push(chunk); diff --git a/server/src/maintenance/maintenance-health.repository.ts b/server/src/maintenance/maintenance-health.repository.ts index 4e35e6066..07e0ad824 100644 --- a/server/src/maintenance/maintenance-health.repository.ts +++ b/server/src/maintenance/maintenance-health.repository.ts @@ -29,10 +29,12 @@ export class MaintenanceHealthRepository { output += data; - if (output.includes(IMMICH_SERVER_START)) { - resolve(); - worker.kill('SIGTERM'); + if (!output.includes(IMMICH_SERVER_START)) { + return; } + + resolve(); + worker.kill('SIGTERM'); }); worker.on('exit', (code, signal) => diff --git a/server/src/repositories/database.repository.ts b/server/src/repositories/database.repository.ts index cb39b0240..37860b26e 100644 --- a/server/src/repositories/database.repository.ts +++ b/server/src/repositories/database.repository.ts @@ -113,11 +113,13 @@ export class DatabaseRepository { async createExtension(extension: DatabaseExtension): Promise { this.logger.log(`Creating ${EXTENSION_NAMES[extension]} extension`); await sql`CREATE EXTENSION IF NOT EXISTS ${sql.raw(extension)} CASCADE`.execute(this.db); - if (extension === DatabaseExtension.VectorChord) { - const dbName = sql.id(await this.getDatabaseName()); - await sql`ALTER DATABASE ${dbName} SET vchordrq.probes = 1`.execute(this.db); - await sql`SET vchordrq.probes = 1`.execute(this.db); + if (extension !== DatabaseExtension.VectorChord) { + return; } + + const dbName = sql.id(await this.getDatabaseName()); + await sql`ALTER DATABASE ${dbName} SET vchordrq.probes = 1`.execute(this.db); + await sql`SET vchordrq.probes = 1`.execute(this.db); } async dropExtension(extension: DatabaseExtension): Promise { @@ -362,7 +364,7 @@ export class DatabaseRepository { if (count < 128_000) { return 1; } - // eslint-disable-next-line unicorn/prefer-minimal-ternary + return count < 2_048_000 ? 1 << (32 - Math.clz32(count / 1000)) : 1 << (33 - Math.clz32(Math.sqrt(count))); } diff --git a/server/src/repositories/map.repository.ts b/server/src/repositories/map.repository.ts index 3f9d2c0d2..d7234faf0 100644 --- a/server/src/repositories/map.repository.ts +++ b/server/src/repositories/map.repository.ts @@ -303,6 +303,7 @@ export class MapRepository { admin2Name: admin2Map.get(`${lineSplit[8]}.${lineSplit[10]}.${lineSplit[11]}`) ?? null, }; bufferGeodata.push(geoData); + // eslint-disable-next-line unicorn/prefer-continue if (bufferGeodata.length >= 5000) { const curLength = bufferGeodata.length; futures.push( diff --git a/server/src/repositories/media.repository.ts b/server/src/repositories/media.repository.ts index f65f80c00..b0f6445e6 100644 --- a/server/src/repositories/media.repository.ts +++ b/server/src/repositories/media.repository.ts @@ -329,14 +329,17 @@ export class MediaRepository { if (flags[1] !== 'D') { postDiscard.push({ pts, duration }); } - if (flags[0] === 'K') { - keyframePts.push(pts); - keyframeAccDuration.push(totalDuration); - // VFR content can have variable duration keyframes, - // so we need to track their duration separately for accurate segment boundaries. - // Non-keyframes are accounted for in totalDuration. - keyframeOwnDuration.push(duration); + + if (flags[0] !== 'K') { + return; } + + keyframePts.push(pts); + keyframeAccDuration.push(totalDuration); + // VFR content can have variable duration keyframes, + // so we need to track their duration separately for accurate segment boundaries. + // Non-keyframes are accounted for in totalDuration. + keyframeOwnDuration.push(duration); }; let stderr = ''; diff --git a/server/src/repositories/ocr.repository.ts b/server/src/repositories/ocr.repository.ts index df627ae86..44e3d6eb6 100644 --- a/server/src/repositories/ocr.repository.ts +++ b/server/src/repositories/ocr.repository.ts @@ -58,7 +58,6 @@ export class OcrRepository { }) upsert(assetId: string, ocrDataList: Insertable[], searchText: string) { let query = this.db.with('deleted_ocr', (db) => db.deleteFrom('asset_ocr').where('assetId', '=', assetId)); - // eslint-disable-next-line unicorn/prefer-ternary if (ocrDataList.length > 0) { (query as any) = query .with('inserted_ocr', (db) => db.insertInto('asset_ocr').values(ocrDataList)) diff --git a/server/src/repositories/storage.repository.ts b/server/src/repositories/storage.repository.ts index ba53f35bd..8464f19ec 100644 --- a/server/src/repositories/storage.repository.ts +++ b/server/src/repositories/storage.repository.ts @@ -186,15 +186,17 @@ export class StorageRepository { const files = await fs.readdir(directory); await Promise.all(files.map((file) => this.removeEmptyDirs(path.join(directory, file), true))); - if (self) { - const updated = await fs.readdir(directory); - if (updated.length === 0) { - try { - await fs.rmdir(directory); - } catch (error: Error | any) { - if (error.code !== 'ENOTEMPTY') { - this.logger.warn(`Attempted to remove directory, but failed: ${error}`); - } + if (!self) { + return; + } + + const updated = await fs.readdir(directory); + if (updated.length === 0) { + try { + await fs.rmdir(directory); + } catch (error: Error | any) { + if (error.code !== 'ENOTEMPTY') { + this.logger.warn(`Attempted to remove directory, but failed: ${error}`); } } } @@ -258,10 +260,12 @@ export class StorageRepository { let batch: string[] = []; for await (const value of stream) { batch.push(value.toString()); - if (batch.length === walkOptions.take) { - yield batch; - batch = []; + if (batch.length !== walkOptions.take) { + continue; } + + yield batch; + batch = []; } if (batch.length > 0) { diff --git a/server/src/services/asset.service.ts b/server/src/services/asset.service.ts index ef1e02dd9..073907d26 100644 --- a/server/src/services/asset.service.ts +++ b/server/src/services/asset.service.ts @@ -507,16 +507,18 @@ export class AssetService extends BaseService { isUndefined, ); - if (Object.keys(writes).length > 0) { - await this.assetRepository.upsertExif({ - exif: updateLockedColumns({ - assetId: id, - ...writes, - }), - lockedPropertiesBehavior: 'append', - }); - await this.jobRepository.queue({ name: JobName.SidecarWrite, data: { id } }); + if (Object.keys(writes).length === 0) { + return; } + + await this.assetRepository.upsertExif({ + exif: updateLockedColumns({ + assetId: id, + ...writes, + }), + lockedPropertiesBehavior: 'append', + }); + await this.jobRepository.queue({ name: JobName.SidecarWrite, data: { id } }); } async getAssetEdits(auth: AuthDto, id: string): Promise { diff --git a/server/src/services/download.service.ts b/server/src/services/download.service.ts index 10c4c7f2f..3cdd48a15 100644 --- a/server/src/services/download.service.ts +++ b/server/src/services/download.service.ts @@ -47,10 +47,12 @@ export class DownloadService extends BaseService { archive.assetIds.push(id); archive.size += Number(size || 0); - if (archive.size > targetSize) { - archives.push(archive); - archive = { size: 0, assetIds: [] }; + if (archive.size <= targetSize) { + return; } + + archives.push(archive); + archive = { size: 0, assetIds: [] }; }; for await (const asset of assets) { diff --git a/server/src/services/hls.service.ts b/server/src/services/hls.service.ts index b84d5ec2a..56e05b1da 100644 --- a/server/src/services/hls.service.ts +++ b/server/src/services/hls.service.ts @@ -27,10 +27,12 @@ export class HlsService extends BaseService { @OnEvent({ name: 'HlsSessionResult', server: true, workers: [ImmichWorker.Api] }) onSessionResult(event: ArgOf<'HlsSessionResult'>) { this.pendingSessions.complete(event.sessionId, event); - if (event.error) { - this.sessions.delete(event.sessionId); - this.pendingSegments.rejectByPrefix(`${event.sessionId}:`, event.error); + if (!event.error) { + return; } + + this.sessions.delete(event.sessionId); + this.pendingSegments.rejectByPrefix(`${event.sessionId}:`, event.error); } @OnEvent({ name: 'HlsSessionEnd', server: true, workers: [ImmichWorker.Api] }) diff --git a/server/src/services/library.service.ts b/server/src/services/library.service.ts index 87ba09af6..d86589a18 100644 --- a/server/src/services/library.service.ts +++ b/server/src/services/library.service.ts @@ -79,11 +79,13 @@ export class LibraryService extends BaseService { start: library.scan.enabled, }); - if (library.watch.enabled !== this.watchLibraries) { - // Watch configuration changed, update accordingly - this.watchLibraries = library.watch.enabled; - await (this.watchLibraries ? this.watchAll() : this.unwatchAll()); + if (library.watch.enabled === this.watchLibraries) { + return; } + + // Watch configuration changed, update accordingly + this.watchLibraries = library.watch.enabled; + await (this.watchLibraries ? this.watchAll() : this.unwatchAll()); } private async watch(id: string): Promise { diff --git a/server/src/services/media.service.ts b/server/src/services/media.service.ts index 5ee672666..9dc0bcfb8 100644 --- a/server/src/services/media.service.ts +++ b/server/src/services/media.service.ts @@ -782,6 +782,7 @@ export class MediaService extends BaseService { } // upsert new file path + // eslint-disable-next-line unicorn/prefer-continue if ( existingFile?.path !== newFile.path || existingFile.isProgressive !== newFile.isProgressive || diff --git a/server/src/services/metadata.service.ts b/server/src/services/metadata.service.ts index e48100202..0fe7d04b1 100644 --- a/server/src/services/metadata.service.ts +++ b/server/src/services/metadata.service.ts @@ -943,15 +943,17 @@ export class MetadataService extends BaseService { }; facesToAdd.push(face); - if (!existingNameMap.has(loweredName)) { - missing.push({ - personGroupId, - ownerId: asset.ownerId, - clusterGroupId: asset.clusterGroupId, - name: region.Name, - }); - missingWithFaceAsset.push({ personGroupId, ownerId: asset.ownerId, faceAssetId: face.id }); + if (existingNameMap.has(loweredName)) { + continue; } + + missing.push({ + personGroupId, + ownerId: asset.ownerId, + clusterGroupId: asset.clusterGroupId, + name: region.Name, + }); + missingWithFaceAsset.push({ personGroupId, ownerId: asset.ownerId, faceAssetId: face.id }); } if (missing.length > 0) { diff --git a/server/src/services/person.service.ts b/server/src/services/person.service.ts index d27c63c61..5f47632c0 100644 --- a/server/src/services/person.service.ts +++ b/server/src/services/person.service.ts @@ -155,10 +155,12 @@ export class PersonService extends BaseService { for (const { ownerId, personGroupId } of changeFeaturePhoto) { const assetFace = await this.personRepository.getRandomFace(personGroupId); - if (assetFace) { - await this.personRepository.update({ ownerId, personGroupId, faceAssetId: assetFace.id }); - jobs.push({ name: JobName.PersonGenerateThumbnail, data: { ownerId, personGroupId } }); + if (!assetFace) { + continue; } + + await this.personRepository.update({ ownerId, personGroupId, faceAssetId: assetFace.id }); + jobs.push({ name: JobName.PersonGenerateThumbnail, data: { ownerId, personGroupId } }); } await this.jobRepository.queueAll(jobs); diff --git a/server/src/services/queue.service.ts b/server/src/services/queue.service.ts index ce8389532..cc7fc7515 100644 --- a/server/src/services/queue.service.ts +++ b/server/src/services/queue.service.ts @@ -49,16 +49,18 @@ export class QueueService extends BaseService { } this.nightlyJobsLock = await this.databaseRepository.tryLock(DatabaseLock.NightlyJobs); - if (this.nightlyJobsLock) { - const cronExpression = asNightlyTasksCron(config); - this.logger.debug(`Scheduling nightly jobs for ${cronExpression}`); - this.cronRepository.create({ - name: CronJob.NightlyJobs, - expression: cronExpression, - start: true, - onTick: () => handlePromiseError(this.handleNightlyJobs(), this.logger), - }); + if (!this.nightlyJobsLock) { + return; } + + const cronExpression = asNightlyTasksCron(config); + this.logger.debug(`Scheduling nightly jobs for ${cronExpression}`); + this.cronRepository.create({ + name: CronJob.NightlyJobs, + expression: cronExpression, + start: true, + onTick: () => handlePromiseError(this.handleNightlyJobs(), this.logger), + }); } @OnEvent({ name: 'ConfigUpdate', server: true }) @@ -68,11 +70,13 @@ export class QueueService extends BaseService { return; } - if (this.nightlyJobsLock) { - const cronExpression = asNightlyTasksCron(config); - this.logger.debug(`Scheduling nightly jobs for ${cronExpression}`); - this.cronRepository.update({ name: CronJob.NightlyJobs, expression: cronExpression, start: true }); + if (!this.nightlyJobsLock) { + return; } + + const cronExpression = asNightlyTasksCron(config); + this.logger.debug(`Scheduling nightly jobs for ${cronExpression}`); + this.cronRepository.update({ name: CronJob.NightlyJobs, expression: cronExpression, start: true }); } @OnEvent({ name: 'AppBootstrap', priority: BootstrapEventPriority.JobService }) diff --git a/server/src/services/storage-template.service.ts b/server/src/services/storage-template.service.ts index aa72f1347..1b135f0fa 100644 --- a/server/src/services/storage-template.service.ts +++ b/server/src/services/storage-template.service.ts @@ -92,10 +92,12 @@ export class StorageTemplateService extends BaseService { @OnEvent({ name: 'ConfigInit' }) onConfigInit({ newConfig }: ArgOf<'ConfigInit'>) { const template = newConfig.storageTemplate.template; - if (!this._template || template !== this.template.raw) { - this.logger.debug(`Compiling new storage template: ${template}`); - this._template = this.compile(template); + if (this._template && template === this.template.raw) { + return; } + + this.logger.debug(`Compiling new storage template: ${template}`); + this._template = this.compile(template); } @OnEvent({ name: 'ConfigUpdate', server: true }) @@ -191,16 +193,20 @@ export class StorageTemplateService extends BaseService { const filename = asset.originalFileName || asset.id; await this.moveAsset(asset, { storageLabel, filename }); - // move motion part of live photo - if (asset.livePhotoVideoId) { - const livePhotoVideo = await this.assetJobRepository.getForStorageTemplateJob(asset.livePhotoVideoId, { - includeHidden: true, - }); - if (livePhotoVideo) { - const motionFilename = getLivePhotoMotionFilename(filename, livePhotoVideo.originalPath); - await this.moveAsset(livePhotoVideo, { storageLabel, filename: motionFilename }, asset); - } + if (!asset.livePhotoVideoId) { + continue; } + + // move motion part of live photo + const livePhotoVideo = await this.assetJobRepository.getForStorageTemplateJob(asset.livePhotoVideoId, { + includeHidden: true, + }); + if (!livePhotoVideo) { + continue; + } + + const motionFilename = getLivePhotoMotionFilename(filename, livePhotoVideo.originalPath); + await this.moveAsset(livePhotoVideo, { storageLabel, filename: motionFilename }, asset); } this.logger.debug('Cleaning up empty directories...'); @@ -417,13 +423,15 @@ export class StorageTemplateService extends BaseService { for (const token of Object.values(storageTokens).flat()) { substitutions[token] = dt.toFormat(token); - if (albumName) { - // Album date tokens are rendered in the server time zone to match storage template datetime behavior. - substitutions['album-startDate-' + token] = albumStartDate - ? DateTime.fromJSDate(albumStartDate).toFormat(token) - : ''; - substitutions['album-endDate-' + token] = albumEndDate ? DateTime.fromJSDate(albumEndDate).toFormat(token) : ''; + if (!albumName) { + continue; } + + // Album date tokens are rendered in the server time zone to match storage template datetime behavior. + substitutions['album-startDate-' + token] = albumStartDate + ? DateTime.fromJSDate(albumStartDate).toFormat(token) + : ''; + substitutions['album-endDate-' + token] = albumEndDate ? DateTime.fromJSDate(albumEndDate).toFormat(token) : ''; } return template(substitutions).replaceAll(/\/{2,}/gm, '/'); diff --git a/server/src/services/storage.service.ts b/server/src/services/storage.service.ts index cb189a59d..9ac44e6f1 100644 --- a/server/src/services/storage.service.ts +++ b/server/src/services/storage.service.ts @@ -71,10 +71,12 @@ export class StorageService extends BaseService { await this.verifyReadAccess(folder); await this.verifyWriteAccess(folder); - if (!flags.mountChecks[folder]) { - flags.mountChecks[folder] = true; - isUpdated = true; + if (flags.mountChecks[folder]) { + continue; } + + flags.mountChecks[folder] = true; + isUpdated = true; } if (isUpdated) { diff --git a/server/src/services/timeline.service.ts b/server/src/services/timeline.service.ts index fcda8cf4c..0fe5597c7 100644 --- a/server/src/services/timeline.service.ts +++ b/server/src/services/timeline.service.ts @@ -73,6 +73,7 @@ export class TimelineService extends BaseService { dto.withCoordinates = false; } + // eslint-disable-next-line unicorn/prefer-early-return if (dto.withPartners) { const isRequestedLocked = dto.visibility === AssetVisibility.Locked; const isRequestedArchived = dto.visibility === AssetVisibility.Archive || dto.visibility === undefined; diff --git a/server/src/services/transcoding.service.ts b/server/src/services/transcoding.service.ts index 07a86e9c2..d09c97f06 100644 --- a/server/src/services/transcoding.service.ts +++ b/server/src/services/transcoding.service.ts @@ -123,6 +123,7 @@ export class TranscodingService extends BaseService { } const remaining = session.expiresAt.getTime() - Date.now(); + // eslint-disable-next-line unicorn/prefer-early-return if (remaining < HLS_LEASE_DURATION_MS / 2) { session.expiresAt = new Date(Date.now() + HLS_LEASE_DURATION_MS); await this.videoStreamRepository.extendSession(sessionId, session.expiresAt); @@ -317,14 +318,17 @@ export class TranscodingService extends BaseService { session.paused = false; session.process = null; session.lastCompletedSegment = null; - if (code) { - this.logger.error( - `FFmpeg exited with code ${code} for variant ${variantIndex} asset ${session.assetId}\n${stderr}`, - ); - void this.failSession(session, `Transcoding process exited unexpectedly with code ${code}`).catch((error) => - this.logger.error(`Failed to end session ${session.id} after ffmpeg exit: ${error}`), - ); + + if (!code) { + return; } + + this.logger.error( + `FFmpeg exited with code ${code} for variant ${variantIndex} asset ${session.assetId}\n${stderr}`, + ); + void this.failSession(session, `Transcoding process exited unexpectedly with code ${code}`).catch((error) => + this.logger.error(`Failed to end session ${session.id} after ffmpeg exit: ${error}`), + ); }); } diff --git a/server/src/services/version.service.ts b/server/src/services/version.service.ts index a0d0974a6..34330b58e 100644 --- a/server/src/services/version.service.ts +++ b/server/src/services/version.service.ts @@ -62,16 +62,18 @@ export class VersionService extends BaseService { return; } - if (previous.version !== current) { - const previousVersion = new SemVer(previous.version); + if (previous.version === current) { + return; + } - this.logger.log(`Adding ${current} to upgrade history`); - await this.versionRepository.create({ version: current }); + const previousVersion = new SemVer(previous.version); - const isNeedsNewMemories = lt(previousVersion, '1.129.0'); - if (isNeedsNewMemories) { - await this.jobRepository.queue({ name: JobName.MemoryGenerate }); - } + this.logger.log(`Adding ${current} to upgrade history`); + await this.versionRepository.create({ version: current }); + + const isNeedsNewMemories = lt(previousVersion, '1.129.0'); + if (isNeedsNewMemories) { + await this.jobRepository.queue({ name: JobName.MemoryGenerate }); } }); } diff --git a/server/src/services/workflow-execution.service.ts b/server/src/services/workflow-execution.service.ts index 31e0688bc..a3a729494 100644 --- a/server/src/services/workflow-execution.service.ts +++ b/server/src/services/workflow-execution.service.ts @@ -147,15 +147,17 @@ export class WorkflowExecutionService extends BaseService { } const isMethodWithFunction = methods.some(({ hostFunctions }) => hostFunctions); - if (isMethodWithFunction) { - const label = `${name}@${version}/worker`; - const key = this.getPluginKey({ id, hostFunctions: true }); - try { - await this.pluginRepository.load({ key, label, wasmBytes }, { runInWorker: true, functions }); - this.logger.log(`Loaded plugin with host functions: ${label}`); - } catch (error) { - this.logger.error(`Unable to load plugin with host functions ${label} (${id})`, error); - } + if (!isMethodWithFunction) { + continue; + } + + const label = `${name}@${version}/worker`; + const key = this.getPluginKey({ id, hostFunctions: true }); + try { + await this.pluginRepository.load({ key, label, wasmBytes }, { runInWorker: true, functions }); + this.logger.log(`Loaded plugin with host functions: ${label}`); + } catch (error) { + this.logger.error(`Unable to load plugin with host functions ${label} (${id})`, error); } } } diff --git a/server/src/utils/asset.util.ts b/server/src/utils/asset.util.ts index 745f1e849..ed63df38e 100644 --- a/server/src/utils/asset.util.ts +++ b/server/src/utils/asset.util.ts @@ -157,10 +157,12 @@ export const onBeforeLink = async ( throw new BadRequestException('Live photo video does not belong to the user'); } - if (motionAsset && motionAsset.visibility === AssetVisibility.Timeline) { - await assetRepository.update({ id: livePhotoVideoId, visibility: AssetVisibility.Hidden }); - await eventRepository.emit('AssetHide', { assetId: motionAsset.id, userId }); + if (!motionAsset || motionAsset.visibility !== AssetVisibility.Timeline) { + return; } + + await assetRepository.update({ id: livePhotoVideoId, visibility: AssetVisibility.Hidden }); + await eventRepository.emit('AssetHide', { assetId: motionAsset.id, userId }); }; export const onBeforeUnlink = async ( diff --git a/server/src/utils/misc.ts b/server/src/utils/misc.ts index 05923522e..fbb64c53a 100644 --- a/server/src/utils/misc.ts +++ b/server/src/utils/misc.ts @@ -123,10 +123,12 @@ export async function* batched(items: AsyncIterable, size = JOBS_ASSET_PAG for await (const item of items) { batch.push(item); - if (batch.length >= size) { - yield batch; - batch = []; + if (batch.length < size) { + continue; } + + yield batch; + batch = []; } if (batch.length > 0) { @@ -243,17 +245,19 @@ const patchOpenAPI = (document: OpenAPIObject) => { value = value.items; } - if (isSchema(value) && value.type === 'number') { - if (value.format === 'float') { - errors.push(`Invalid number format: ${schemaName}.${key}=float (use double instead). `); - } + if (!(isSchema(value) && value.type === 'number')) { + continue; + } - // verify it was meant to be a number (and not an integer) - if (!value.format) { - errors.push( - `${schemaName}.${key} is a number (not an integer) and requires a format (e.g .meta({ format: 'double' })). `, - ); - } + if (value.format === 'float') { + errors.push(`Invalid number format: ${schemaName}.${key}=float (use double instead). `); + } + + // verify it was meant to be a number (and not an integer) + if (!value.format) { + errors.push( + `${schemaName}.${key} is a number (not an integer) and requires a format (e.g .meta({ format: 'double' })). `, + ); } } schema.required?.sort(); @@ -359,11 +363,13 @@ export const useSwagger = (app: INestApplication, { write }: { write: boolean }) SwaggerModule.setup('doc', app, openApiDoc, customOptions); - if (write) { - // Generate API Documentation only in development mode - const outputPath = path.resolve(process.cwd(), '../open-api/immich-openapi-specs.json'); - writeFileSync(outputPath, JSON.stringify(patchOpenAPI(openApiDoc), null, 2), { encoding: 'utf8' }); + // Generate API Documentation only in development mode + if (!write) { + return; } + + const outputPath = path.resolve(process.cwd(), '../open-api/immich-openapi-specs.json'); + writeFileSync(outputPath, JSON.stringify(patchOpenAPI(openApiDoc), null, 2), { encoding: 'utf8' }); }; // Compiles a glob to the equivalent Postgres regex (Postgres's Advanced Regular Expression diff --git a/web/eslint.config.js b/web/eslint.config.js index 7e7580b47..048e8200d 100644 --- a/web/eslint.config.js +++ b/web/eslint.config.js @@ -106,8 +106,6 @@ export default typescriptEslint.config( }, }, - ignores: ['**/service-worker/**'], - rules: { '@typescript-eslint/no-unused-vars': [ 'warn', @@ -173,6 +171,10 @@ export default typescriptEslint.config( 'svelte/button-has-type': 'error', 'object-shorthand': ['error', 'always'], 'svelte/no-navigation-without-resolve': 'off', + 'unicorn/prefer-early-return': 'off', + 'unicorn/prefer-ternary': 'off', + 'unicorn/prefer-combined-guards': 'off', + 'unicorn/no-immediate-mutation': 'off', eqeqeq: 'error', }, }, diff --git a/web/package.json b/web/package.json index 04ee13123..fa17e6d81 100644 --- a/web/package.json +++ b/web/package.json @@ -98,7 +98,7 @@ "eslint-plugin-better-tailwindcss": "^4.5.0", "eslint-plugin-compat": "^7.0.0", "eslint-plugin-svelte": "^3.12.4", - "eslint-plugin-unicorn": "^73.0.0", + "eslint-plugin-unicorn": "^75.0.0", "factory.ts": "^1.4.1", "globals": "^17.0.0", "happy-dom": "^20.0.0", diff --git a/web/src/lib/components/asset-viewer/face-editor/FaceEditor.svelte b/web/src/lib/components/asset-viewer/face-editor/FaceEditor.svelte index d42737c65..1249bb933 100644 --- a/web/src/lib/components/asset-viewer/face-editor/FaceEditor.svelte +++ b/web/src/lib/components/asset-viewer/face-editor/FaceEditor.svelte @@ -209,12 +209,14 @@ for (const position of positions) { const overlap = overlapArea(position); - if (overlap < leastOverlap) { - leastOverlap = overlap; - bestPosition = position; - if (overlap === 0) { - break; - } + if (overlap >= leastOverlap) { + continue; + } + + leastOverlap = overlap; + bestPosition = position; + if (overlap === 0) { + break; } } diff --git a/web/src/lib/managers/timeline-manager/internal/search-support.svelte.ts b/web/src/lib/managers/timeline-manager/internal/search-support.svelte.ts index d6104f405..88b6cd611 100644 --- a/web/src/lib/managers/timeline-manager/internal/search-support.svelte.ts +++ b/web/src/lib/managers/timeline-manager/internal/search-support.svelte.ts @@ -172,10 +172,12 @@ export function findClosestTimelineMonthForDate(months: TimelineMonth[], targetY const monthDate = DateTime.fromObject({ year: month.yearMonth.year, month: month.yearMonth.month }); const totalDiff = Math.abs(monthDate.diff(targetDate, 'months').months); - if (totalDiff < minDifference) { - minDifference = totalDiff; - closestMonth = month; + if (totalDiff >= minDifference) { + continue; } + + minDifference = totalDiff; + closestMonth = month; } return closestMonth; diff --git a/web/src/lib/managers/timeline-manager/timeline-day.svelte.ts b/web/src/lib/managers/timeline-manager/timeline-day.svelte.ts index 2515acc38..a4805d67d 100644 --- a/web/src/lib/managers/timeline-manager/timeline-day.svelte.ts +++ b/web/src/lib/managers/timeline-manager/timeline-day.svelte.ts @@ -150,6 +150,7 @@ export class TimelineDay { } unprocessedIds.delete(assetId); processedIds.add(assetId); + // eslint-disable-next-line unicorn/prefer-continue if (remove || this.timelineMonth.timelineManager.isExcluded(asset)) { this.viewerAssets.splice(index, 1); changedGeometry = true; diff --git a/web/src/lib/managers/timeline-manager/timeline-month.svelte.ts b/web/src/lib/managers/timeline-manager/timeline-month.svelte.ts index 58b36769b..4c90904f4 100644 --- a/web/src/lib/managers/timeline-manager/timeline-month.svelte.ts +++ b/web/src/lib/managers/timeline-manager/timeline-month.svelte.ts @@ -159,10 +159,13 @@ export class TimelineMonth { idsProcessed.add(id); } combinedChangedGeometry ||= changedGeometry; - if (group.viewerAssets.length === 0) { - timelineDays.splice(index, 1); - combinedChangedGeometry = true; + + if (group.viewerAssets.length > 0) { + continue; } + + timelineDays.splice(index, 1); + combinedChangedGeometry = true; } return { moveAssets: combinedMoveAssets.flat(), @@ -382,10 +385,13 @@ export class TimelineMonth { for (const current of this.assetsIterator()) { const currentAssetDate = fromTimelinePlainDateTime(getOrderingDate(current, this.#orderBy)); const diff = Math.abs(targetDate.diff(currentAssetDate).as('milliseconds')); - if (diff < smallestDiff) { - smallestDiff = diff; - closest = current; + + if (diff >= smallestDiff) { + continue; } + + smallestDiff = diff; + closest = current; } return closest; } diff --git a/web/src/lib/utils/tree-utils.ts b/web/src/lib/utils/tree-utils.ts index 64b51158c..eb513cdea 100644 --- a/web/src/lib/utils/tree-utils.ts +++ b/web/src/lib/utils/tree-utils.ts @@ -49,10 +49,12 @@ export class TreeNode extends Map { // segments common to all subtrees can be collapsed together curPart = curPart === null ? part : joinPaths(curPart, part); const next = current.get(curPart); - if (next) { - current = next; - curPart = null; + if (!next) { + continue; } + + current = next; + curPart = null; } return current; } diff --git a/web/src/service-worker/index.ts b/web/src/service-worker/index.ts index 14acb739a..75515b484 100644 --- a/web/src/service-worker/index.ts +++ b/web/src/service-worker/index.ts @@ -1,3 +1,4 @@ +/* eslint-disable unicorn/no-unnecessary-global-this */ /// /// /// diff --git a/web/src/service-worker/messaging.ts b/web/src/service-worker/messaging.ts index a2eb69ef7..ae8735ebe 100644 --- a/web/src/service-worker/messaging.ts +++ b/web/src/service-worker/messaging.ts @@ -1,3 +1,4 @@ +/* eslint-disable unicorn/no-unnecessary-global-this */ /// /// /// diff --git a/web/src/service-worker/request.ts b/web/src/service-worker/request.ts index c4963b94b..2dd2d35e6 100644 --- a/web/src/service-worker/request.ts +++ b/web/src/service-worker/request.ts @@ -23,7 +23,6 @@ export const handleFetch = async (request: URL | Request): Promise => if (existing) { // Clone the response since response bodies can only be read once // Each caller gets an independent clone they can consume - // eslint-disable-next-line unicorn/prefer-await return existing.promise.then((response) => response.clone()); } @@ -36,7 +35,6 @@ export const handleFetch = async (request: URL | Request): Promise => // NOTE: fetch returns after headers received, not the body pendingRequest.promise = fetch(request, { signal: pendingRequest.controller.signal }) - // eslint-disable-next-line unicorn/prefer-await .catch((error: unknown) => { const standardError = error instanceof Error ? error : new Error(String(error)); if (standardError.name === 'AbortError' || standardError.message === CANCELATION_MESSAGE) { @@ -45,7 +43,6 @@ export const handleFetch = async (request: URL | Request): Promise => } throw standardError; }) - // eslint-disable-next-line unicorn/prefer-await .finally(() => { // Schedule cleanup after timeout to allow response body streaming to complete const cleanupTimeout = setTimeout(() => { @@ -55,7 +52,6 @@ export const handleFetch = async (request: URL | Request): Promise => }); // Clone for the first caller to keep the original response unconsumed for future callers - // eslint-disable-next-line unicorn/prefer-await return pendingRequest.promise.then((response) => response.clone()); };