@immich/cli 2.5.6 → 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.nvmrc +1 -1
- package/dist/index.js +163 -78
- package/package.json +14 -14
package/.nvmrc
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
24.13.
|
|
1
|
+
24.13.1
|
package/dist/index.js
CHANGED
|
@@ -886,12 +886,12 @@ function requireSuggestSimilar() {
|
|
|
886
886
|
function editDistance(a2, b) {
|
|
887
887
|
if (Math.abs(a2.length - b.length) > maxDistance)
|
|
888
888
|
return Math.max(a2.length, b.length);
|
|
889
|
-
const
|
|
889
|
+
const d2 = [];
|
|
890
890
|
for (let i = 0; i <= a2.length; i++) {
|
|
891
|
-
|
|
891
|
+
d2[i] = [i];
|
|
892
892
|
}
|
|
893
893
|
for (let j2 = 0; j2 <= b.length; j2++) {
|
|
894
|
-
|
|
894
|
+
d2[0][j2] = j2;
|
|
895
895
|
}
|
|
896
896
|
for (let j2 = 1; j2 <= b.length; j2++) {
|
|
897
897
|
for (let i = 1; i <= a2.length; i++) {
|
|
@@ -901,20 +901,20 @@ function requireSuggestSimilar() {
|
|
|
901
901
|
} else {
|
|
902
902
|
cost = 1;
|
|
903
903
|
}
|
|
904
|
-
|
|
905
|
-
|
|
904
|
+
d2[i][j2] = Math.min(
|
|
905
|
+
d2[i - 1][j2] + 1,
|
|
906
906
|
// deletion
|
|
907
|
-
|
|
907
|
+
d2[i][j2 - 1] + 1,
|
|
908
908
|
// insertion
|
|
909
|
-
|
|
909
|
+
d2[i - 1][j2 - 1] + cost
|
|
910
910
|
// substitution
|
|
911
911
|
);
|
|
912
912
|
if (i > 1 && j2 > 1 && a2[i - 1] === b[j2 - 2] && a2[i - 2] === b[j2 - 1]) {
|
|
913
|
-
|
|
913
|
+
d2[i][j2] = Math.min(d2[i][j2], d2[i - 2][j2 - 2] + 1);
|
|
914
914
|
}
|
|
915
915
|
}
|
|
916
916
|
}
|
|
917
|
-
return
|
|
917
|
+
return d2[a2.length][b.length];
|
|
918
918
|
}
|
|
919
919
|
function suggestSimilar$1(word, candidates) {
|
|
920
920
|
if (!candidates || candidates.length === 0) return "";
|
|
@@ -1570,8 +1570,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1570
1570
|
} else if (fn instanceof RegExp) {
|
|
1571
1571
|
const regex = fn;
|
|
1572
1572
|
fn = (val, def) => {
|
|
1573
|
-
const
|
|
1574
|
-
return
|
|
1573
|
+
const m = regex.exec(val);
|
|
1574
|
+
return m ? m[0] : def;
|
|
1575
1575
|
};
|
|
1576
1576
|
option2.default(defaultValue).argParser(fn);
|
|
1577
1577
|
} else {
|
|
@@ -3039,31 +3039,34 @@ const {
|
|
|
3039
3039
|
Help
|
|
3040
3040
|
} = commander;
|
|
3041
3041
|
const a = [encodeURIComponent, encodeURIComponent];
|
|
3042
|
-
function
|
|
3043
|
-
const
|
|
3044
|
-
const c = r
|
|
3045
|
-
return typeof
|
|
3046
|
-
(i,
|
|
3042
|
+
function d(e, t2 = ",") {
|
|
3043
|
+
const o2 = (n, r) => {
|
|
3044
|
+
const c = e[r % e.length];
|
|
3045
|
+
return typeof n > "u" ? "" : typeof n == "object" ? Array.isArray(n) ? n.map(c).join(t2) : Object.entries(n).reduce(
|
|
3046
|
+
(i, f) => [...i, ...f],
|
|
3047
3047
|
[]
|
|
3048
|
-
).map(c).join(
|
|
3048
|
+
).map(c).join(t2) : c(l$1(n));
|
|
3049
3049
|
};
|
|
3050
|
-
return (
|
|
3050
|
+
return (n, ...r) => n.reduce((c, u, i) => `${c}${u}${o2(r[i], i)}`, "");
|
|
3051
3051
|
}
|
|
3052
|
-
function
|
|
3053
|
-
return (
|
|
3052
|
+
function R(e = ",") {
|
|
3053
|
+
return (t2, o2 = a) => Object.entries(t2).filter(([, n]) => n !== void 0).map(([n, r]) => d(o2, e)`${n}=${r}`).join("&");
|
|
3054
3054
|
}
|
|
3055
|
-
function j$1(...
|
|
3056
|
-
return
|
|
3055
|
+
function j$1(...e) {
|
|
3056
|
+
return e.filter(Boolean).map((t2, o2) => o2 === 0 ? t2 : t2.replace(/^\/+/, "")).map((t2, o2, n) => o2 === n.length - 1 ? t2 : t2.replace(/\/+$/, "")).join("/");
|
|
3057
|
+
}
|
|
3058
|
+
function l$1(e) {
|
|
3059
|
+
return typeof e == "string" || typeof e == "number" || typeof e == "boolean" ? e : String(e);
|
|
3057
3060
|
}
|
|
3058
3061
|
function q(...r) {
|
|
3059
3062
|
const n = r.filter(Boolean).join("&");
|
|
3060
3063
|
return n && `?${n}`;
|
|
3061
3064
|
}
|
|
3062
|
-
function
|
|
3063
|
-
const o2 =
|
|
3064
|
-
return Object.entries(r).filter(([, t2]) => t2 !== void 0).map(([t2, e]) => Array.isArray(e) ? e.map((i) => o2`${t2}=${i}`).join("&") : typeof e == "object" ?
|
|
3065
|
+
function y$1(r, n = a) {
|
|
3066
|
+
const o2 = d(n);
|
|
3067
|
+
return Object.entries(r).filter(([, t2]) => t2 !== void 0).map(([t2, e]) => Array.isArray(e) ? e.map((i) => o2`${t2}=${i}`).join("&") : typeof e == "object" ? y$1(e, n) : o2`${t2}=${e}`).join("&");
|
|
3065
3068
|
}
|
|
3066
|
-
const
|
|
3069
|
+
const A = R();
|
|
3067
3070
|
function o(e, r) {
|
|
3068
3071
|
const n = t(e);
|
|
3069
3072
|
return t(r).forEach((i, s2) => {
|
|
@@ -3108,7 +3111,7 @@ function D(a2 = {}) {
|
|
|
3108
3111
|
data: c ? JSON.parse(c) : null
|
|
3109
3112
|
} : { status: t2, headers: s2, data: c };
|
|
3110
3113
|
}
|
|
3111
|
-
async function
|
|
3114
|
+
async function f(e, n = {}) {
|
|
3112
3115
|
const t2 = await p(e, n);
|
|
3113
3116
|
let s2;
|
|
3114
3117
|
try {
|
|
@@ -3133,7 +3136,7 @@ function D(a2 = {}) {
|
|
|
3133
3136
|
ok: y,
|
|
3134
3137
|
fetchText: r,
|
|
3135
3138
|
fetchJson: i,
|
|
3136
|
-
fetchBlob:
|
|
3139
|
+
fetchBlob: f,
|
|
3137
3140
|
mergeHeaders: o,
|
|
3138
3141
|
json({ body: e, headers: n, ...t2 }) {
|
|
3139
3142
|
return {
|
|
@@ -3150,7 +3153,7 @@ function D(a2 = {}) {
|
|
|
3150
3153
|
form({ body: e, headers: n, ...t2 }) {
|
|
3151
3154
|
return {
|
|
3152
3155
|
...t2,
|
|
3153
|
-
...e != null && { body:
|
|
3156
|
+
...e != null && { body: A(e) },
|
|
3154
3157
|
headers: o(
|
|
3155
3158
|
{
|
|
3156
3159
|
"Content-Type": "application/x-www-form-urlencoded"
|
|
@@ -3169,7 +3172,7 @@ function D(a2 = {}) {
|
|
|
3169
3172
|
);
|
|
3170
3173
|
};
|
|
3171
3174
|
return Object.entries(e).forEach(([c, o2]) => {
|
|
3172
|
-
Array.isArray(o2) ? o2.forEach((
|
|
3175
|
+
Array.isArray(o2) ? o2.forEach((m) => u(c, m)) : u(c, o2);
|
|
3173
3176
|
}), {
|
|
3174
3177
|
...t$1,
|
|
3175
3178
|
body: s2,
|
|
@@ -3188,8 +3191,8 @@ class l extends Error {
|
|
|
3188
3191
|
status;
|
|
3189
3192
|
data;
|
|
3190
3193
|
headers;
|
|
3191
|
-
constructor(r, i,
|
|
3192
|
-
super(`Error: ${r}`), this.status = r, this.data = i, this.headers =
|
|
3194
|
+
constructor(r, i, f) {
|
|
3195
|
+
super(`Error: ${r}`), this.status = r, this.data = i, this.headers = f;
|
|
3193
3196
|
}
|
|
3194
3197
|
}
|
|
3195
3198
|
const defaults = {
|
|
@@ -3198,7 +3201,7 @@ const defaults = {
|
|
|
3198
3201
|
};
|
|
3199
3202
|
const oazapfts = D(defaults);
|
|
3200
3203
|
function getAllAlbums({ assetId, shared }, opts) {
|
|
3201
|
-
return oazapfts.ok(oazapfts.fetchJson(`/albums${q(
|
|
3204
|
+
return oazapfts.ok(oazapfts.fetchJson(`/albums${q(y$1({
|
|
3202
3205
|
assetId,
|
|
3203
3206
|
shared
|
|
3204
3207
|
}))}`, {
|
|
@@ -3213,7 +3216,7 @@ function createAlbum({ createAlbumDto }, opts) {
|
|
|
3213
3216
|
})));
|
|
3214
3217
|
}
|
|
3215
3218
|
function addAssetsToAlbum({ id, key, slug, bulkIdsDto }, opts) {
|
|
3216
|
-
return oazapfts.ok(oazapfts.fetchJson(`/albums/${encodeURIComponent(id)}/assets${q(
|
|
3219
|
+
return oazapfts.ok(oazapfts.fetchJson(`/albums/${encodeURIComponent(id)}/assets${q(y$1({
|
|
3217
3220
|
key,
|
|
3218
3221
|
slug
|
|
3219
3222
|
}))}`, oazapfts.json({
|
|
@@ -3222,6 +3225,11 @@ function addAssetsToAlbum({ id, key, slug, bulkIdsDto }, opts) {
|
|
|
3222
3225
|
body: bulkIdsDto
|
|
3223
3226
|
})));
|
|
3224
3227
|
}
|
|
3228
|
+
function getMyApiKey(opts) {
|
|
3229
|
+
return oazapfts.ok(oazapfts.fetchJson("/api-keys/me", {
|
|
3230
|
+
...opts
|
|
3231
|
+
}));
|
|
3232
|
+
}
|
|
3225
3233
|
function checkBulkUpload({ assetBulkUploadCheckDto }, opts) {
|
|
3226
3234
|
return oazapfts.ok(oazapfts.fetchJson("/assets/bulk-upload-check", oazapfts.json({
|
|
3227
3235
|
...opts,
|
|
@@ -3230,7 +3238,7 @@ function checkBulkUpload({ assetBulkUploadCheckDto }, opts) {
|
|
|
3230
3238
|
})));
|
|
3231
3239
|
}
|
|
3232
3240
|
function getAssetStatistics({ isFavorite, isTrashed, visibility }, opts) {
|
|
3233
|
-
return oazapfts.ok(oazapfts.fetchJson(`/assets/statistics${q(
|
|
3241
|
+
return oazapfts.ok(oazapfts.fetchJson(`/assets/statistics${q(y$1({
|
|
3234
3242
|
isFavorite,
|
|
3235
3243
|
isTrashed,
|
|
3236
3244
|
visibility
|
|
@@ -3718,6 +3726,8 @@ var SyncEntityType;
|
|
|
3718
3726
|
SyncEntityType2["AssetV1"] = "AssetV1";
|
|
3719
3727
|
SyncEntityType2["AssetDeleteV1"] = "AssetDeleteV1";
|
|
3720
3728
|
SyncEntityType2["AssetExifV1"] = "AssetExifV1";
|
|
3729
|
+
SyncEntityType2["AssetEditV1"] = "AssetEditV1";
|
|
3730
|
+
SyncEntityType2["AssetEditDeleteV1"] = "AssetEditDeleteV1";
|
|
3721
3731
|
SyncEntityType2["AssetMetadataV1"] = "AssetMetadataV1";
|
|
3722
3732
|
SyncEntityType2["AssetMetadataDeleteV1"] = "AssetMetadataDeleteV1";
|
|
3723
3733
|
SyncEntityType2["PartnerV1"] = "PartnerV1";
|
|
@@ -3753,6 +3763,7 @@ var SyncEntityType;
|
|
|
3753
3763
|
SyncEntityType2["PersonV1"] = "PersonV1";
|
|
3754
3764
|
SyncEntityType2["PersonDeleteV1"] = "PersonDeleteV1";
|
|
3755
3765
|
SyncEntityType2["AssetFaceV1"] = "AssetFaceV1";
|
|
3766
|
+
SyncEntityType2["AssetFaceV2"] = "AssetFaceV2";
|
|
3756
3767
|
SyncEntityType2["AssetFaceDeleteV1"] = "AssetFaceDeleteV1";
|
|
3757
3768
|
SyncEntityType2["UserMetadataV1"] = "UserMetadataV1";
|
|
3758
3769
|
SyncEntityType2["UserMetadataDeleteV1"] = "UserMetadataDeleteV1";
|
|
@@ -3769,6 +3780,7 @@ var SyncRequestType;
|
|
|
3769
3780
|
SyncRequestType2["AlbumAssetExifsV1"] = "AlbumAssetExifsV1";
|
|
3770
3781
|
SyncRequestType2["AssetsV1"] = "AssetsV1";
|
|
3771
3782
|
SyncRequestType2["AssetExifsV1"] = "AssetExifsV1";
|
|
3783
|
+
SyncRequestType2["AssetEditsV1"] = "AssetEditsV1";
|
|
3772
3784
|
SyncRequestType2["AssetMetadataV1"] = "AssetMetadataV1";
|
|
3773
3785
|
SyncRequestType2["AuthUsersV1"] = "AuthUsersV1";
|
|
3774
3786
|
SyncRequestType2["MemoriesV1"] = "MemoriesV1";
|
|
@@ -3781,6 +3793,7 @@ var SyncRequestType;
|
|
|
3781
3793
|
SyncRequestType2["UsersV1"] = "UsersV1";
|
|
3782
3794
|
SyncRequestType2["PeopleV1"] = "PeopleV1";
|
|
3783
3795
|
SyncRequestType2["AssetFacesV1"] = "AssetFacesV1";
|
|
3796
|
+
SyncRequestType2["AssetFacesV2"] = "AssetFacesV2";
|
|
3784
3797
|
SyncRequestType2["UserMetadataV1"] = "UserMetadataV1";
|
|
3785
3798
|
})(SyncRequestType || (SyncRequestType = {}));
|
|
3786
3799
|
var TranscodeHWAccel;
|
|
@@ -3796,6 +3809,7 @@ var AudioCodec;
|
|
|
3796
3809
|
AudioCodec2["Mp3"] = "mp3";
|
|
3797
3810
|
AudioCodec2["Aac"] = "aac";
|
|
3798
3811
|
AudioCodec2["Libopus"] = "libopus";
|
|
3812
|
+
AudioCodec2["Opus"] = "opus";
|
|
3799
3813
|
AudioCodec2["PcmS16Le"] = "pcm_s16le";
|
|
3800
3814
|
})(AudioCodec || (AudioCodec = {}));
|
|
3801
3815
|
var VideoContainer;
|
|
@@ -4033,7 +4047,7 @@ const normalizeFilter = (filter) => {
|
|
|
4033
4047
|
}
|
|
4034
4048
|
if (Array.isArray(filter)) {
|
|
4035
4049
|
const trItems = filter.map((item) => item.trim());
|
|
4036
|
-
return (entry2) => trItems.some((
|
|
4050
|
+
return (entry2) => trItems.some((f) => entry2.basename === f);
|
|
4037
4051
|
}
|
|
4038
4052
|
return emptyFn;
|
|
4039
4053
|
};
|
|
@@ -8544,10 +8558,10 @@ function requireParse() {
|
|
|
8544
8558
|
};
|
|
8545
8559
|
if (opts.fastpaths !== false && !/(^[*!]|[/()[\]{}"])/.test(input)) {
|
|
8546
8560
|
let backslashes = false;
|
|
8547
|
-
let output = input.replace(REGEX_SPECIAL_CHARS_BACKREF, (
|
|
8561
|
+
let output = input.replace(REGEX_SPECIAL_CHARS_BACKREF, (m, esc, chars, first, rest, index) => {
|
|
8548
8562
|
if (first === "\\") {
|
|
8549
8563
|
backslashes = true;
|
|
8550
|
-
return
|
|
8564
|
+
return m;
|
|
8551
8565
|
}
|
|
8552
8566
|
if (first === "?") {
|
|
8553
8567
|
if (esc) {
|
|
@@ -8567,14 +8581,14 @@ function requireParse() {
|
|
|
8567
8581
|
}
|
|
8568
8582
|
return star;
|
|
8569
8583
|
}
|
|
8570
|
-
return esc ?
|
|
8584
|
+
return esc ? m : `\\${m}`;
|
|
8571
8585
|
});
|
|
8572
8586
|
if (backslashes === true) {
|
|
8573
8587
|
if (opts.unescape === true) {
|
|
8574
8588
|
output = output.replace(/\\/g, "");
|
|
8575
8589
|
} else {
|
|
8576
|
-
output = output.replace(/\\+/g, (
|
|
8577
|
-
return
|
|
8590
|
+
output = output.replace(/\\+/g, (m) => {
|
|
8591
|
+
return m.length % 2 === 0 ? "\\\\" : m ? "\\" : "";
|
|
8578
8592
|
});
|
|
8579
8593
|
}
|
|
8580
8594
|
}
|
|
@@ -14647,8 +14661,8 @@ function requireStringifyNumber() {
|
|
|
14647
14661
|
i = n.length;
|
|
14648
14662
|
n += ".";
|
|
14649
14663
|
}
|
|
14650
|
-
let
|
|
14651
|
-
while (
|
|
14664
|
+
let d2 = minFractionDigits - (n.length - i - 1);
|
|
14665
|
+
while (d2-- > 0)
|
|
14652
14666
|
n += "0";
|
|
14653
14667
|
}
|
|
14654
14668
|
return n;
|
|
@@ -15132,9 +15146,9 @@ function requireFloat() {
|
|
|
15132
15146
|
const node = new Scalar2.Scalar(parseFloat(str.replace(/_/g, "")));
|
|
15133
15147
|
const dot = str.indexOf(".");
|
|
15134
15148
|
if (dot !== -1) {
|
|
15135
|
-
const
|
|
15136
|
-
if (
|
|
15137
|
-
node.minFractionDigits =
|
|
15149
|
+
const f = str.substring(dot + 1).replace(/_/g, "");
|
|
15150
|
+
if (f[f.length - 1] === "0")
|
|
15151
|
+
node.minFractionDigits = f.length;
|
|
15138
15152
|
}
|
|
15139
15153
|
return node;
|
|
15140
15154
|
},
|
|
@@ -15387,10 +15401,10 @@ function requireTimestamp() {
|
|
|
15387
15401
|
let date = Date.UTC(year, month - 1, day, hour || 0, minute || 0, second || 0, millisec);
|
|
15388
15402
|
const tz = match[8];
|
|
15389
15403
|
if (tz && tz !== "Z") {
|
|
15390
|
-
let
|
|
15391
|
-
if (Math.abs(
|
|
15392
|
-
|
|
15393
|
-
date -= 6e4 *
|
|
15404
|
+
let d2 = parseSexagesimal(tz, false);
|
|
15405
|
+
if (Math.abs(d2) < 30)
|
|
15406
|
+
d2 *= 60;
|
|
15407
|
+
date -= 6e4 * d2;
|
|
15394
15408
|
}
|
|
15395
15409
|
return new Date(date);
|
|
15396
15410
|
},
|
|
@@ -16868,8 +16882,8 @@ function requireResolveBlockScalar() {
|
|
|
16868
16882
|
function splitLines(source) {
|
|
16869
16883
|
const split = source.split(/\n( *)/);
|
|
16870
16884
|
const first = split[0];
|
|
16871
|
-
const
|
|
16872
|
-
const line0 =
|
|
16885
|
+
const m = first.match(/^( *)/);
|
|
16886
|
+
const line0 = m?.[1] ? [m[1], first.slice(m[1].length)] : ["", first];
|
|
16873
16887
|
const lines = [line0];
|
|
16874
16888
|
for (let i = 1; i < split.length; i += 2)
|
|
16875
16889
|
lines.push([split[i], split[i + 1]]);
|
|
@@ -19603,6 +19617,30 @@ const authenticate = async (options2) => {
|
|
|
19603
19617
|
}
|
|
19604
19618
|
return auth;
|
|
19605
19619
|
};
|
|
19620
|
+
const s = (count) => count === 1 ? "" : "s";
|
|
19621
|
+
let _apiKey;
|
|
19622
|
+
const requirePermissions = async (permissions) => {
|
|
19623
|
+
if (!_apiKey) {
|
|
19624
|
+
_apiKey = await getMyApiKey();
|
|
19625
|
+
}
|
|
19626
|
+
if (_apiKey.permissions.includes(Permission.All)) {
|
|
19627
|
+
return;
|
|
19628
|
+
}
|
|
19629
|
+
const missing = [];
|
|
19630
|
+
for (const permission of permissions) {
|
|
19631
|
+
if (!_apiKey.permissions.includes(permission)) {
|
|
19632
|
+
missing.push(permission);
|
|
19633
|
+
}
|
|
19634
|
+
}
|
|
19635
|
+
if (missing.length > 0) {
|
|
19636
|
+
const combined = missing.map((permission) => `"${permission}"`).join(", ");
|
|
19637
|
+
console.log(
|
|
19638
|
+
`Missing required permission${s(missing.length)}: ${combined}.
|
|
19639
|
+
Please make sure your API key has the correct permissions.`
|
|
19640
|
+
);
|
|
19641
|
+
process.exit(1);
|
|
19642
|
+
}
|
|
19643
|
+
};
|
|
19606
19644
|
const connect = async (url, key) => {
|
|
19607
19645
|
const wellKnownUrl = new URL(".well-known/immich", url);
|
|
19608
19646
|
try {
|
|
@@ -19617,7 +19655,7 @@ const connect = async (url, key) => {
|
|
|
19617
19655
|
init({ baseUrl: url, apiKey: key });
|
|
19618
19656
|
const [error2] = await withError(getMyUser());
|
|
19619
19657
|
if (isHttpError(error2)) {
|
|
19620
|
-
logError(error2,
|
|
19658
|
+
logError(error2, `Failed to connect to server ${url}`);
|
|
19621
19659
|
process.exit(1);
|
|
19622
19660
|
}
|
|
19623
19661
|
return { url, key };
|
|
@@ -19760,7 +19798,6 @@ class Batcher {
|
|
|
19760
19798
|
}
|
|
19761
19799
|
const UPLOAD_WATCH_BATCH_SIZE = 100;
|
|
19762
19800
|
const UPLOAD_WATCH_DEBOUNCE_TIME_MS = 1e4;
|
|
19763
|
-
const s = (count) => count === 1 ? "" : "s";
|
|
19764
19801
|
class UploadFile extends File {
|
|
19765
19802
|
constructor(filepath, _size) {
|
|
19766
19803
|
super([], basename(filepath));
|
|
@@ -19830,6 +19867,7 @@ const startWatch = async (paths, options2, {
|
|
|
19830
19867
|
};
|
|
19831
19868
|
const upload = async (paths, baseOptions, options2) => {
|
|
19832
19869
|
await authenticate(baseOptions);
|
|
19870
|
+
await requirePermissions([Permission.AssetUpload]);
|
|
19833
19871
|
const scanFiles = await scan(paths, options2);
|
|
19834
19872
|
if (scanFiles.length === 0) {
|
|
19835
19873
|
if (options2.watch) {
|
|
@@ -19863,16 +19901,43 @@ const checkForDuplicates = async (files, { concurrency, skipHash, progress }) =>
|
|
|
19863
19901
|
return { newFiles: files, duplicates: [] };
|
|
19864
19902
|
}
|
|
19865
19903
|
let multiBar2;
|
|
19904
|
+
let totalSize = 0;
|
|
19905
|
+
const statsMap = /* @__PURE__ */ new Map();
|
|
19906
|
+
for (const filepath of files) {
|
|
19907
|
+
const stats = await stat(filepath);
|
|
19908
|
+
statsMap.set(filepath, stats);
|
|
19909
|
+
totalSize += stats.size;
|
|
19910
|
+
}
|
|
19866
19911
|
if (progress) {
|
|
19867
19912
|
multiBar2 = new cliProgressExports.MultiBar(
|
|
19868
|
-
{
|
|
19913
|
+
{
|
|
19914
|
+
format: "{message} | {bar} | {percentage}% | ETA: {eta_formatted} | {value}/{total}",
|
|
19915
|
+
formatValue: (v, options2, type2) => {
|
|
19916
|
+
if (type2 === "percentage") {
|
|
19917
|
+
return v.toString();
|
|
19918
|
+
}
|
|
19919
|
+
return byteSize(v).toString();
|
|
19920
|
+
},
|
|
19921
|
+
etaBuffer: 100
|
|
19922
|
+
// Increase samples for ETA calculation
|
|
19923
|
+
},
|
|
19869
19924
|
cliProgressExports.Presets.shades_classic
|
|
19870
19925
|
);
|
|
19926
|
+
process.on("SIGINT", () => {
|
|
19927
|
+
if (multiBar2) {
|
|
19928
|
+
multiBar2.stop();
|
|
19929
|
+
}
|
|
19930
|
+
process.exit(0);
|
|
19931
|
+
});
|
|
19871
19932
|
} else {
|
|
19872
|
-
console.log(`Received ${files.length} files, hashing...`);
|
|
19933
|
+
console.log(`Received ${files.length} files (${byteSize(totalSize)}), hashing...`);
|
|
19873
19934
|
}
|
|
19874
|
-
const hashProgressBar = multiBar2?.create(
|
|
19875
|
-
|
|
19935
|
+
const hashProgressBar = multiBar2?.create(totalSize, 0, {
|
|
19936
|
+
message: "Hashing files "
|
|
19937
|
+
});
|
|
19938
|
+
const checkProgressBar = multiBar2?.create(totalSize, 0, {
|
|
19939
|
+
message: "Checking for duplicates"
|
|
19940
|
+
});
|
|
19876
19941
|
const newFiles = [];
|
|
19877
19942
|
const duplicates = [];
|
|
19878
19943
|
const checkBulkUploadQueue = new Queue(
|
|
@@ -19886,7 +19951,12 @@ const checkForDuplicates = async (files, { concurrency, skipHash, progress }) =>
|
|
|
19886
19951
|
duplicates.push({ id: assetId, filepath });
|
|
19887
19952
|
}
|
|
19888
19953
|
}
|
|
19889
|
-
|
|
19954
|
+
let processedSize = 0;
|
|
19955
|
+
for (const asset of assets) {
|
|
19956
|
+
const stats = statsMap.get(asset.id);
|
|
19957
|
+
processedSize += stats?.size || 0;
|
|
19958
|
+
}
|
|
19959
|
+
checkProgressBar?.increment(processedSize);
|
|
19890
19960
|
},
|
|
19891
19961
|
{ concurrency, retry: 3 }
|
|
19892
19962
|
);
|
|
@@ -19894,6 +19964,10 @@ const checkForDuplicates = async (files, { concurrency, skipHash, progress }) =>
|
|
|
19894
19964
|
let checkBulkUploadRequests = [];
|
|
19895
19965
|
const queue2 = new Queue(
|
|
19896
19966
|
async (filepath) => {
|
|
19967
|
+
const stats = statsMap.get(filepath);
|
|
19968
|
+
if (!stats) {
|
|
19969
|
+
throw new Error(`Stats not found for ${filepath}`);
|
|
19970
|
+
}
|
|
19897
19971
|
const dto = { id: filepath, checksum: await sha1(filepath) };
|
|
19898
19972
|
results.push(dto);
|
|
19899
19973
|
checkBulkUploadRequests.push(dto);
|
|
@@ -19902,7 +19976,7 @@ const checkForDuplicates = async (files, { concurrency, skipHash, progress }) =>
|
|
|
19902
19976
|
checkBulkUploadRequests = [];
|
|
19903
19977
|
void checkBulkUploadQueue.push(batch);
|
|
19904
19978
|
}
|
|
19905
|
-
hashProgressBar?.increment();
|
|
19979
|
+
hashProgressBar?.increment(stats.size);
|
|
19906
19980
|
return results;
|
|
19907
19981
|
},
|
|
19908
19982
|
{ concurrency, retry: 3 }
|
|
@@ -20000,20 +20074,6 @@ const uploadFiles = async (files, { dryRun, concurrency, progress }) => {
|
|
|
20000
20074
|
};
|
|
20001
20075
|
const uploadFile = async (input, stats) => {
|
|
20002
20076
|
const { baseUrl, headers } = defaults;
|
|
20003
|
-
const assetPath = path$1.parse(input);
|
|
20004
|
-
const noExtension = path$1.join(assetPath.dir, assetPath.name);
|
|
20005
|
-
const sidecarsFiles = await Promise.all(
|
|
20006
|
-
// XMP sidecars can come in two filename formats. For a photo named photo.ext, the filenames are photo.ext.xmp and photo.xmp
|
|
20007
|
-
[`${noExtension}.xmp`, `${input}.xmp`].map(async (sidecarPath) => {
|
|
20008
|
-
try {
|
|
20009
|
-
const stats2 = await stat(sidecarPath);
|
|
20010
|
-
return new UploadFile(sidecarPath, stats2.size);
|
|
20011
|
-
} catch {
|
|
20012
|
-
return false;
|
|
20013
|
-
}
|
|
20014
|
-
})
|
|
20015
|
-
);
|
|
20016
|
-
const sidecarData = sidecarsFiles.find((file) => file !== false);
|
|
20017
20077
|
const formData = new FormData();
|
|
20018
20078
|
formData.append("deviceAssetId", `${basename(input)}-${stats.size}`.replaceAll(/\s+/g, ""));
|
|
20019
20079
|
formData.append("deviceId", "CLI");
|
|
@@ -20022,8 +20082,14 @@ const uploadFile = async (input, stats) => {
|
|
|
20022
20082
|
formData.append("fileSize", String(stats.size));
|
|
20023
20083
|
formData.append("isFavorite", "false");
|
|
20024
20084
|
formData.append("assetData", new UploadFile(input, stats.size));
|
|
20025
|
-
|
|
20026
|
-
|
|
20085
|
+
const sidecarPath = findSidecar(input);
|
|
20086
|
+
if (sidecarPath) {
|
|
20087
|
+
try {
|
|
20088
|
+
const stats2 = await stat(sidecarPath);
|
|
20089
|
+
const sidecarData = new UploadFile(sidecarPath, stats2.size);
|
|
20090
|
+
formData.append("sidecarData", sidecarData);
|
|
20091
|
+
} catch {
|
|
20092
|
+
}
|
|
20027
20093
|
}
|
|
20028
20094
|
const response = await fetch(`${baseUrl}/assets`, {
|
|
20029
20095
|
method: "post",
|
|
@@ -20036,6 +20102,15 @@ const uploadFile = async (input, stats) => {
|
|
|
20036
20102
|
}
|
|
20037
20103
|
return response.json();
|
|
20038
20104
|
};
|
|
20105
|
+
const findSidecar = (filepath) => {
|
|
20106
|
+
const assetPath = path$1.parse(filepath);
|
|
20107
|
+
const noExtension = path$1.join(assetPath.dir, assetPath.name);
|
|
20108
|
+
for (const sidecarPath of [`${noExtension}.xmp`, `${filepath}.xmp`]) {
|
|
20109
|
+
if (existsSync(sidecarPath)) {
|
|
20110
|
+
return sidecarPath;
|
|
20111
|
+
}
|
|
20112
|
+
}
|
|
20113
|
+
};
|
|
20039
20114
|
const deleteFiles = async (uploaded, duplicates, options2) => {
|
|
20040
20115
|
let fileCount = 0;
|
|
20041
20116
|
if (options2.delete) {
|
|
@@ -20059,7 +20134,15 @@ const deleteFiles = async (uploaded, duplicates, options2) => {
|
|
|
20059
20134
|
deletionProgress.start(fileCount, 0);
|
|
20060
20135
|
const chunkDelete = async (files) => {
|
|
20061
20136
|
for (const assetBatch of chunk(files, options2.concurrency)) {
|
|
20062
|
-
await Promise.all(
|
|
20137
|
+
await Promise.all(
|
|
20138
|
+
assetBatch.map(async (input) => {
|
|
20139
|
+
await unlink(input.filepath);
|
|
20140
|
+
const sidecarPath = findSidecar(input.filepath);
|
|
20141
|
+
if (sidecarPath) {
|
|
20142
|
+
await unlink(sidecarPath);
|
|
20143
|
+
}
|
|
20144
|
+
})
|
|
20145
|
+
);
|
|
20063
20146
|
deletionProgress.update(assetBatch.length);
|
|
20064
20147
|
}
|
|
20065
20148
|
};
|
|
@@ -20150,6 +20233,7 @@ const login = async (url, key, options2) => {
|
|
|
20150
20233
|
console.log(`Logging in to ${url}`);
|
|
20151
20234
|
const { configDirectory: configDir } = options2;
|
|
20152
20235
|
await connect(url, key);
|
|
20236
|
+
await requirePermissions([Permission.UserRead]);
|
|
20153
20237
|
const [error2, user] = await withError(getMyUser());
|
|
20154
20238
|
if (error2) {
|
|
20155
20239
|
logError(error2, "Failed to load user info");
|
|
@@ -20178,6 +20262,7 @@ const logout = async (options2) => {
|
|
|
20178
20262
|
};
|
|
20179
20263
|
const serverInfo = async (options2) => {
|
|
20180
20264
|
const { url } = await authenticate(options2);
|
|
20265
|
+
await requirePermissions([Permission.ServerAbout, Permission.AssetStatistics, Permission.UserRead]);
|
|
20181
20266
|
const [versionInfo, mediaTypes, stats, userInfo] = await Promise.all([
|
|
20182
20267
|
getServerVersion(),
|
|
20183
20268
|
getSupportedMediaTypes(),
|
|
@@ -20195,7 +20280,7 @@ const serverInfo = async (options2) => {
|
|
|
20195
20280
|
console.log(` Videos: ${stats.videos}`);
|
|
20196
20281
|
console.log(` Total: ${stats.total}`);
|
|
20197
20282
|
};
|
|
20198
|
-
const version = "2.
|
|
20283
|
+
const version = "2.6.1";
|
|
20199
20284
|
const defaultConfigDirectory = path$1.join(os.homedir(), ".config/immich/");
|
|
20200
20285
|
const defaultConcurrency = Math.max(1, os.cpus().length - 1);
|
|
20201
20286
|
const program = new Command().name("immich").version(version).description("Command line interface for Immich").addOption(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@immich/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.1",
|
|
4
4
|
"description": "Command Line Interface (CLI) for Immich",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": "./dist/index.js",
|
|
@@ -13,23 +13,22 @@
|
|
|
13
13
|
"cli"
|
|
14
14
|
],
|
|
15
15
|
"devDependencies": {
|
|
16
|
-
"@eslint/js": "^
|
|
17
|
-
"@immich/sdk": "file:../open-api/typescript-sdk",
|
|
16
|
+
"@eslint/js": "^10.0.0",
|
|
18
17
|
"@types/byte-size": "^8.1.0",
|
|
19
18
|
"@types/cli-progress": "^3.11.0",
|
|
20
19
|
"@types/lodash-es": "^4.17.12",
|
|
21
20
|
"@types/micromatch": "^4.0.9",
|
|
22
21
|
"@types/mock-fs": "^4.13.1",
|
|
23
|
-
"@types/node": "^24.
|
|
24
|
-
"@vitest/coverage-v8": "^
|
|
22
|
+
"@types/node": "^24.11.0",
|
|
23
|
+
"@vitest/coverage-v8": "^4.0.0",
|
|
25
24
|
"byte-size": "^9.0.0",
|
|
26
25
|
"cli-progress": "^3.12.0",
|
|
27
26
|
"commander": "^12.0.0",
|
|
28
|
-
"eslint": "^
|
|
27
|
+
"eslint": "^10.0.0",
|
|
29
28
|
"eslint-config-prettier": "^10.1.8",
|
|
30
29
|
"eslint-plugin-prettier": "^5.1.3",
|
|
31
|
-
"eslint-plugin-unicorn": "^
|
|
32
|
-
"globals": "^
|
|
30
|
+
"eslint-plugin-unicorn": "^63.0.0",
|
|
31
|
+
"globals": "^17.0.0",
|
|
33
32
|
"mock-fs": "^5.2.0",
|
|
34
33
|
"prettier": "^3.7.4",
|
|
35
34
|
"prettier-plugin-organize-imports": "^4.0.0",
|
|
@@ -37,9 +36,10 @@
|
|
|
37
36
|
"typescript-eslint": "^8.28.0",
|
|
38
37
|
"vite": "^7.0.0",
|
|
39
38
|
"vite-tsconfig-paths": "^6.0.0",
|
|
40
|
-
"vitest": "^
|
|
39
|
+
"vitest": "^4.0.0",
|
|
41
40
|
"vitest-fetch-mock": "^0.4.0",
|
|
42
|
-
"yaml": "^2.3.1"
|
|
41
|
+
"yaml": "^2.3.1",
|
|
42
|
+
"@immich/sdk": "2.6.1"
|
|
43
43
|
},
|
|
44
44
|
"repository": {
|
|
45
45
|
"type": "git",
|
|
@@ -57,17 +57,17 @@
|
|
|
57
57
|
"micromatch": "^4.0.8"
|
|
58
58
|
},
|
|
59
59
|
"volta": {
|
|
60
|
-
"node": "24.13.
|
|
60
|
+
"node": "24.13.1"
|
|
61
61
|
},
|
|
62
62
|
"scripts": {
|
|
63
63
|
"build": "vite build",
|
|
64
64
|
"build:dev": "vite build --sourcemap true",
|
|
65
65
|
"lint": "eslint \"src/**/*.ts\" --max-warnings 0",
|
|
66
|
-
"lint:fix": "
|
|
66
|
+
"lint:fix": "pnpm run lint --fix",
|
|
67
67
|
"test": "vitest",
|
|
68
68
|
"test:cov": "vitest --coverage",
|
|
69
|
-
"format": "prettier --check .",
|
|
70
|
-
"format:fix": "prettier --write .",
|
|
69
|
+
"format": "prettier --cache --check .",
|
|
70
|
+
"format:fix": "prettier --cache --write --list-different .",
|
|
71
71
|
"check": "tsc --noEmit"
|
|
72
72
|
}
|
|
73
73
|
}
|