@gallop.software/studio 1.6.0 → 1.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/dist/handlers/index.js +77 -55
- package/dist/handlers/index.js.map +1 -1
- package/dist/handlers/index.mjs +128 -106
- package/dist/handlers/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/handlers/index.mjs
CHANGED
|
@@ -10,13 +10,36 @@ import { NextResponse as NextResponse6 } from "next/server";
|
|
|
10
10
|
// src/handlers/list.ts
|
|
11
11
|
import { NextResponse } from "next/server";
|
|
12
12
|
import { promises as fs4 } from "fs";
|
|
13
|
-
import
|
|
13
|
+
import path4 from "path";
|
|
14
14
|
|
|
15
15
|
// src/handlers/utils/meta.ts
|
|
16
16
|
import { promises as fs } from "fs";
|
|
17
|
+
|
|
18
|
+
// src/config/workspace.ts
|
|
17
19
|
import path from "path";
|
|
20
|
+
var workspacePath = null;
|
|
21
|
+
function getWorkspace() {
|
|
22
|
+
if (workspacePath === null) {
|
|
23
|
+
workspacePath = process.env.STUDIO_WORKSPACE || process.cwd();
|
|
24
|
+
}
|
|
25
|
+
return workspacePath;
|
|
26
|
+
}
|
|
27
|
+
function getPublicPath(...segments) {
|
|
28
|
+
return path.join(getWorkspace(), "public", ...segments);
|
|
29
|
+
}
|
|
30
|
+
function getDataPath(...segments) {
|
|
31
|
+
return path.join(getWorkspace(), "_data", ...segments);
|
|
32
|
+
}
|
|
33
|
+
function getSrcAppPath(...segments) {
|
|
34
|
+
return path.join(getWorkspace(), "src", "app", ...segments);
|
|
35
|
+
}
|
|
36
|
+
function getWorkspacePath(...segments) {
|
|
37
|
+
return path.join(getWorkspace(), ...segments);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/handlers/utils/meta.ts
|
|
18
41
|
async function loadMeta() {
|
|
19
|
-
const metaPath =
|
|
42
|
+
const metaPath = getDataPath("_studio.json");
|
|
20
43
|
try {
|
|
21
44
|
const content = await fs.readFile(metaPath, "utf-8");
|
|
22
45
|
return JSON.parse(content);
|
|
@@ -25,9 +48,9 @@ async function loadMeta() {
|
|
|
25
48
|
}
|
|
26
49
|
}
|
|
27
50
|
async function saveMeta(meta) {
|
|
28
|
-
const dataDir =
|
|
51
|
+
const dataDir = getDataPath();
|
|
29
52
|
await fs.mkdir(dataDir, { recursive: true });
|
|
30
|
-
const metaPath =
|
|
53
|
+
const metaPath = getDataPath("_studio.json");
|
|
31
54
|
const ordered = {};
|
|
32
55
|
if (meta._cdns) {
|
|
33
56
|
ordered._cdns = meta._cdns;
|
|
@@ -123,7 +146,7 @@ async function processImage(buffer, imageKey) {
|
|
|
123
146
|
const baseName = path3.basename(keyWithoutSlash, path3.extname(keyWithoutSlash));
|
|
124
147
|
const ext = path3.extname(keyWithoutSlash).toLowerCase();
|
|
125
148
|
const imageDir = path3.dirname(keyWithoutSlash);
|
|
126
|
-
const imagesPath =
|
|
149
|
+
const imagesPath = getPublicPath("images", imageDir === "." ? "" : imageDir);
|
|
127
150
|
await fs2.mkdir(imagesPath, { recursive: true });
|
|
128
151
|
const isPng = ext === ".png";
|
|
129
152
|
const outputExt = isPng ? ".png" : ".jpg";
|
|
@@ -131,7 +154,7 @@ async function processImage(buffer, imageKey) {
|
|
|
131
154
|
o: { w: originalWidth, h: originalHeight }
|
|
132
155
|
};
|
|
133
156
|
const fullFileName = imageDir === "." ? `${baseName}${outputExt}` : `${imageDir}/${baseName}${outputExt}`;
|
|
134
|
-
const fullPath =
|
|
157
|
+
const fullPath = getPublicPath("images", fullFileName);
|
|
135
158
|
let fullWidth = originalWidth;
|
|
136
159
|
let fullHeight = originalHeight;
|
|
137
160
|
if (originalWidth > FULL_MAX_WIDTH) {
|
|
@@ -158,7 +181,7 @@ async function processImage(buffer, imageKey) {
|
|
|
158
181
|
const newHeight = Math.round(maxWidth * ratio);
|
|
159
182
|
const sizeFileName = `${baseName}${suffix}${outputExt}`;
|
|
160
183
|
const sizeFilePath = imageDir === "." ? sizeFileName : `${imageDir}/${sizeFileName}`;
|
|
161
|
-
const sizePath =
|
|
184
|
+
const sizePath = getPublicPath("images", sizeFilePath);
|
|
162
185
|
if (isPng) {
|
|
163
186
|
await sharp(buffer).resize(maxWidth, newHeight).png({ quality: 80 }).toFile(sizePath);
|
|
164
187
|
} else {
|
|
@@ -173,7 +196,6 @@ async function processImage(buffer, imageKey) {
|
|
|
173
196
|
|
|
174
197
|
// src/handlers/utils/cdn.ts
|
|
175
198
|
import { promises as fs3 } from "fs";
|
|
176
|
-
import path4 from "path";
|
|
177
199
|
import { S3Client, GetObjectCommand, PutObjectCommand, DeleteObjectCommand } from "@aws-sdk/client-s3";
|
|
178
200
|
async function purgeCloudflareCache(urls) {
|
|
179
201
|
const zoneId = process.env.CLOUDFLARE_ZONE_ID;
|
|
@@ -247,7 +269,7 @@ async function uploadToCdn(imageKey) {
|
|
|
247
269
|
if (!bucketName) throw new Error("R2 bucket not configured");
|
|
248
270
|
const r2 = getR2Client();
|
|
249
271
|
for (const thumbPath of getAllThumbnailPaths(imageKey)) {
|
|
250
|
-
const localPath =
|
|
272
|
+
const localPath = getPublicPath(thumbPath);
|
|
251
273
|
try {
|
|
252
274
|
const fileBuffer = await fs3.readFile(localPath);
|
|
253
275
|
await r2.send(
|
|
@@ -264,7 +286,7 @@ async function uploadToCdn(imageKey) {
|
|
|
264
286
|
}
|
|
265
287
|
async function deleteLocalThumbnails(imageKey) {
|
|
266
288
|
for (const thumbPath of getAllThumbnailPaths(imageKey)) {
|
|
267
|
-
const localPath =
|
|
289
|
+
const localPath = getPublicPath(thumbPath);
|
|
268
290
|
try {
|
|
269
291
|
await fs3.unlink(localPath);
|
|
270
292
|
} catch {
|
|
@@ -295,7 +317,7 @@ async function uploadOriginalToCdn(imageKey) {
|
|
|
295
317
|
const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;
|
|
296
318
|
if (!bucketName) throw new Error("R2 bucket not configured");
|
|
297
319
|
const r2 = getR2Client();
|
|
298
|
-
const localPath =
|
|
320
|
+
const localPath = getPublicPath(imageKey);
|
|
299
321
|
const fileBuffer = await fs3.readFile(localPath);
|
|
300
322
|
await r2.send(
|
|
301
323
|
new PutObjectCommand({
|
|
@@ -495,7 +517,7 @@ async function handleList(request) {
|
|
|
495
517
|
}
|
|
496
518
|
return NextResponse.json({ items });
|
|
497
519
|
}
|
|
498
|
-
const absoluteDir =
|
|
520
|
+
const absoluteDir = getWorkspacePath(requestedPath);
|
|
499
521
|
try {
|
|
500
522
|
const dirEntries = await fs4.readdir(absoluteDir, { withFileTypes: true });
|
|
501
523
|
for (const entry of dirEntries) {
|
|
@@ -606,7 +628,7 @@ async function handleList(request) {
|
|
|
606
628
|
hasThumbnail = true;
|
|
607
629
|
}
|
|
608
630
|
} else {
|
|
609
|
-
const localThumbPath =
|
|
631
|
+
const localThumbPath = getPublicPath(thumbPath);
|
|
610
632
|
try {
|
|
611
633
|
await fs4.access(localThumbPath);
|
|
612
634
|
thumbnail = thumbPath;
|
|
@@ -627,7 +649,7 @@ async function handleList(request) {
|
|
|
627
649
|
}
|
|
628
650
|
if (!isPushedToCloud) {
|
|
629
651
|
try {
|
|
630
|
-
const filePath =
|
|
652
|
+
const filePath = getPublicPath(key);
|
|
631
653
|
const stats = await fs4.stat(filePath);
|
|
632
654
|
fileSize = stats.size;
|
|
633
655
|
} catch {
|
|
@@ -669,7 +691,7 @@ async function handleSearch(request) {
|
|
|
669
691
|
const items = [];
|
|
670
692
|
for (const [key, entry] of fileEntries) {
|
|
671
693
|
if (!key.toLowerCase().includes(query)) continue;
|
|
672
|
-
const fileName =
|
|
694
|
+
const fileName = path4.basename(key);
|
|
673
695
|
const relativePath = key.slice(1);
|
|
674
696
|
const isImage = isImageFile(fileName);
|
|
675
697
|
const isPushedToCloud = entry.c !== void 0;
|
|
@@ -687,7 +709,7 @@ async function handleSearch(request) {
|
|
|
687
709
|
hasThumbnail = true;
|
|
688
710
|
}
|
|
689
711
|
} else {
|
|
690
|
-
const localThumbPath =
|
|
712
|
+
const localThumbPath = getPublicPath(thumbPath);
|
|
691
713
|
try {
|
|
692
714
|
await fs4.access(localThumbPath);
|
|
693
715
|
thumbnail = thumbPath;
|
|
@@ -745,13 +767,13 @@ async function handleListFolders() {
|
|
|
745
767
|
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "images") {
|
|
746
768
|
const folderRelPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
747
769
|
folderSet.add(folderRelPath);
|
|
748
|
-
await scanDir(
|
|
770
|
+
await scanDir(path4.join(dir, entry.name), folderRelPath);
|
|
749
771
|
}
|
|
750
772
|
}
|
|
751
773
|
} catch {
|
|
752
774
|
}
|
|
753
775
|
}
|
|
754
|
-
const publicDir =
|
|
776
|
+
const publicDir = getPublicPath();
|
|
755
777
|
await scanDir(publicDir, "");
|
|
756
778
|
const folders = [];
|
|
757
779
|
folders.push({ path: "public", name: "public", depth: 0 });
|
|
@@ -777,7 +799,7 @@ async function handleCountImages() {
|
|
|
777
799
|
const fileEntries = getFileEntries(meta);
|
|
778
800
|
const allImages = [];
|
|
779
801
|
for (const [key] of fileEntries) {
|
|
780
|
-
const fileName =
|
|
802
|
+
const fileName = path4.basename(key);
|
|
781
803
|
if (isImageFile(fileName)) {
|
|
782
804
|
allImages.push(key.slice(1));
|
|
783
805
|
}
|
|
@@ -828,7 +850,7 @@ async function handleFolderImages(request) {
|
|
|
828
850
|
// src/handlers/files.ts
|
|
829
851
|
import { NextResponse as NextResponse2 } from "next/server";
|
|
830
852
|
import { promises as fs5 } from "fs";
|
|
831
|
-
import
|
|
853
|
+
import path5 from "path";
|
|
832
854
|
import sharp2 from "sharp";
|
|
833
855
|
async function handleUpload(request) {
|
|
834
856
|
try {
|
|
@@ -841,7 +863,7 @@ async function handleUpload(request) {
|
|
|
841
863
|
const bytes = await file.arrayBuffer();
|
|
842
864
|
const buffer = Buffer.from(bytes);
|
|
843
865
|
const fileName = file.name;
|
|
844
|
-
const ext =
|
|
866
|
+
const ext = path5.extname(fileName).toLowerCase();
|
|
845
867
|
const isImage = isImageFile(fileName);
|
|
846
868
|
const isMedia = isMediaFile(fileName);
|
|
847
869
|
const meta = await loadMeta();
|
|
@@ -859,7 +881,7 @@ async function handleUpload(request) {
|
|
|
859
881
|
}
|
|
860
882
|
let imageKey = "/" + (relativeDir ? `${relativeDir}/${fileName}` : fileName);
|
|
861
883
|
if (meta[imageKey]) {
|
|
862
|
-
const baseName =
|
|
884
|
+
const baseName = path5.basename(fileName, ext);
|
|
863
885
|
let counter = 1;
|
|
864
886
|
let newFileName = `${baseName}-${counter}${ext}`;
|
|
865
887
|
let newKey = "/" + (relativeDir ? `${relativeDir}/${newFileName}` : newFileName);
|
|
@@ -870,10 +892,10 @@ async function handleUpload(request) {
|
|
|
870
892
|
}
|
|
871
893
|
imageKey = newKey;
|
|
872
894
|
}
|
|
873
|
-
const actualFileName =
|
|
874
|
-
const uploadDir =
|
|
895
|
+
const actualFileName = path5.basename(imageKey);
|
|
896
|
+
const uploadDir = getPublicPath(relativeDir);
|
|
875
897
|
await fs5.mkdir(uploadDir, { recursive: true });
|
|
876
|
-
await fs5.writeFile(
|
|
898
|
+
await fs5.writeFile(path5.join(uploadDir, actualFileName), buffer);
|
|
877
899
|
if (!isMedia) {
|
|
878
900
|
return NextResponse2.json({
|
|
879
901
|
success: true,
|
|
@@ -920,7 +942,7 @@ async function handleDelete(request) {
|
|
|
920
942
|
errors.push(`Invalid path: ${itemPath}`);
|
|
921
943
|
continue;
|
|
922
944
|
}
|
|
923
|
-
const absolutePath =
|
|
945
|
+
const absolutePath = getWorkspacePath(itemPath);
|
|
924
946
|
const imageKey = "/" + itemPath.replace(/^public\//, "");
|
|
925
947
|
const entry = meta[imageKey];
|
|
926
948
|
const isPushedToCloud = entry?.c !== void 0;
|
|
@@ -934,7 +956,7 @@ async function handleDelete(request) {
|
|
|
934
956
|
const keyEntry = meta[key];
|
|
935
957
|
if (keyEntry && keyEntry.c === void 0) {
|
|
936
958
|
for (const thumbPath of getAllThumbnailPaths(key)) {
|
|
937
|
-
const absoluteThumbPath =
|
|
959
|
+
const absoluteThumbPath = getPublicPath(thumbPath);
|
|
938
960
|
try {
|
|
939
961
|
await fs5.unlink(absoluteThumbPath);
|
|
940
962
|
} catch {
|
|
@@ -950,7 +972,7 @@ async function handleDelete(request) {
|
|
|
950
972
|
if (!isInImagesFolder && entry) {
|
|
951
973
|
if (!isPushedToCloud) {
|
|
952
974
|
for (const thumbPath of getAllThumbnailPaths(imageKey)) {
|
|
953
|
-
const absoluteThumbPath =
|
|
975
|
+
const absoluteThumbPath = getPublicPath(thumbPath);
|
|
954
976
|
try {
|
|
955
977
|
await fs5.unlink(absoluteThumbPath);
|
|
956
978
|
} catch {
|
|
@@ -1006,8 +1028,8 @@ async function handleCreateFolder(request) {
|
|
|
1006
1028
|
return NextResponse2.json({ error: "Invalid folder name" }, { status: 400 });
|
|
1007
1029
|
}
|
|
1008
1030
|
const safePath = (parentPath || "public").replace(/\.\./g, "");
|
|
1009
|
-
const folderPath =
|
|
1010
|
-
if (!folderPath.startsWith(
|
|
1031
|
+
const folderPath = getWorkspacePath(safePath, sanitizedName);
|
|
1032
|
+
if (!folderPath.startsWith(getPublicPath())) {
|
|
1011
1033
|
return NextResponse2.json({ error: "Invalid path" }, { status: 400 });
|
|
1012
1034
|
}
|
|
1013
1035
|
try {
|
|
@@ -1016,7 +1038,7 @@ async function handleCreateFolder(request) {
|
|
|
1016
1038
|
} catch {
|
|
1017
1039
|
}
|
|
1018
1040
|
await fs5.mkdir(folderPath, { recursive: true });
|
|
1019
|
-
return NextResponse2.json({ success: true, path:
|
|
1041
|
+
return NextResponse2.json({ success: true, path: path5.join(safePath, sanitizedName) });
|
|
1020
1042
|
} catch (error) {
|
|
1021
1043
|
console.error("Failed to create folder:", error);
|
|
1022
1044
|
return NextResponse2.json({ error: "Failed to create folder" }, { status: 500 });
|
|
@@ -1033,10 +1055,10 @@ async function handleRename(request) {
|
|
|
1033
1055
|
return NextResponse2.json({ error: "Invalid name" }, { status: 400 });
|
|
1034
1056
|
}
|
|
1035
1057
|
const safePath = oldPath.replace(/\.\./g, "");
|
|
1036
|
-
const absoluteOldPath =
|
|
1037
|
-
const parentDir =
|
|
1038
|
-
const absoluteNewPath =
|
|
1039
|
-
if (!absoluteOldPath.startsWith(
|
|
1058
|
+
const absoluteOldPath = getWorkspacePath(safePath);
|
|
1059
|
+
const parentDir = path5.dirname(absoluteOldPath);
|
|
1060
|
+
const absoluteNewPath = path5.join(parentDir, sanitizedName);
|
|
1061
|
+
if (!absoluteOldPath.startsWith(getPublicPath())) {
|
|
1040
1062
|
return NextResponse2.json({ error: "Invalid path" }, { status: 400 });
|
|
1041
1063
|
}
|
|
1042
1064
|
try {
|
|
@@ -1051,12 +1073,12 @@ async function handleRename(request) {
|
|
|
1051
1073
|
}
|
|
1052
1074
|
const stats = await fs5.stat(absoluteOldPath);
|
|
1053
1075
|
const isFile = stats.isFile();
|
|
1054
|
-
const isImage = isFile && isImageFile(
|
|
1076
|
+
const isImage = isFile && isImageFile(path5.basename(oldPath));
|
|
1055
1077
|
await fs5.rename(absoluteOldPath, absoluteNewPath);
|
|
1056
1078
|
if (isImage) {
|
|
1057
1079
|
const meta = await loadMeta();
|
|
1058
1080
|
const oldRelativePath = safePath.replace(/^public\//, "");
|
|
1059
|
-
const newRelativePath =
|
|
1081
|
+
const newRelativePath = path5.join(path5.dirname(oldRelativePath), sanitizedName);
|
|
1060
1082
|
const oldKey = "/" + oldRelativePath;
|
|
1061
1083
|
const newKey = "/" + newRelativePath;
|
|
1062
1084
|
if (meta[oldKey]) {
|
|
@@ -1064,9 +1086,9 @@ async function handleRename(request) {
|
|
|
1064
1086
|
const oldThumbPaths = getAllThumbnailPaths(oldKey);
|
|
1065
1087
|
const newThumbPaths = getAllThumbnailPaths(newKey);
|
|
1066
1088
|
for (let i = 0; i < oldThumbPaths.length; i++) {
|
|
1067
|
-
const oldThumbPath =
|
|
1068
|
-
const newThumbPath =
|
|
1069
|
-
await fs5.mkdir(
|
|
1089
|
+
const oldThumbPath = getPublicPath(oldThumbPaths[i]);
|
|
1090
|
+
const newThumbPath = getPublicPath(newThumbPaths[i]);
|
|
1091
|
+
await fs5.mkdir(path5.dirname(newThumbPath), { recursive: true });
|
|
1070
1092
|
try {
|
|
1071
1093
|
await fs5.rename(oldThumbPath, newThumbPath);
|
|
1072
1094
|
} catch {
|
|
@@ -1077,7 +1099,7 @@ async function handleRename(request) {
|
|
|
1077
1099
|
}
|
|
1078
1100
|
await saveMeta(meta);
|
|
1079
1101
|
}
|
|
1080
|
-
const newPath =
|
|
1102
|
+
const newPath = path5.join(path5.dirname(safePath), sanitizedName);
|
|
1081
1103
|
return NextResponse2.json({ success: true, newPath });
|
|
1082
1104
|
} catch (error) {
|
|
1083
1105
|
console.error("Failed to rename:", error);
|
|
@@ -1106,8 +1128,8 @@ async function handleMoveStream(request) {
|
|
|
1106
1128
|
return;
|
|
1107
1129
|
}
|
|
1108
1130
|
const safeDestination = destination.replace(/\.\./g, "");
|
|
1109
|
-
const absoluteDestination =
|
|
1110
|
-
if (!absoluteDestination.startsWith(
|
|
1131
|
+
const absoluteDestination = getWorkspacePath(safeDestination);
|
|
1132
|
+
if (!absoluteDestination.startsWith(getPublicPath())) {
|
|
1111
1133
|
sendEvent({ type: "error", message: "Invalid destination" });
|
|
1112
1134
|
controller.close();
|
|
1113
1135
|
return;
|
|
@@ -1123,10 +1145,10 @@ async function handleMoveStream(request) {
|
|
|
1123
1145
|
for (let i = 0; i < paths.length; i++) {
|
|
1124
1146
|
const itemPath = paths[i];
|
|
1125
1147
|
const safePath = itemPath.replace(/\.\./g, "");
|
|
1126
|
-
const itemName =
|
|
1127
|
-
const newAbsolutePath =
|
|
1148
|
+
const itemName = path5.basename(safePath);
|
|
1149
|
+
const newAbsolutePath = path5.join(absoluteDestination, itemName);
|
|
1128
1150
|
const oldRelativePath = safePath.replace(/^public\//, "");
|
|
1129
|
-
const newRelativePath =
|
|
1151
|
+
const newRelativePath = path5.join(safeDestination.replace(/^public\//, ""), itemName);
|
|
1130
1152
|
const oldKey = "/" + oldRelativePath;
|
|
1131
1153
|
const newKey = "/" + newRelativePath;
|
|
1132
1154
|
sendEvent({
|
|
@@ -1151,7 +1173,7 @@ async function handleMoveStream(request) {
|
|
|
1151
1173
|
if (isRemote && isImage) {
|
|
1152
1174
|
const remoteUrl = `${fileCdnUrl}${oldKey}`;
|
|
1153
1175
|
const buffer = await downloadFromRemoteUrl(remoteUrl);
|
|
1154
|
-
await fs5.mkdir(
|
|
1176
|
+
await fs5.mkdir(path5.dirname(newAbsolutePath), { recursive: true });
|
|
1155
1177
|
await fs5.writeFile(newAbsolutePath, buffer);
|
|
1156
1178
|
const newEntry = {
|
|
1157
1179
|
o: entry?.o,
|
|
@@ -1162,7 +1184,7 @@ async function handleMoveStream(request) {
|
|
|
1162
1184
|
moved.push(itemPath);
|
|
1163
1185
|
} else if (isPushedToR2 && isImage) {
|
|
1164
1186
|
const buffer = await downloadFromCdn(oldKey);
|
|
1165
|
-
await fs5.mkdir(
|
|
1187
|
+
await fs5.mkdir(path5.dirname(newAbsolutePath), { recursive: true });
|
|
1166
1188
|
await fs5.writeFile(newAbsolutePath, buffer);
|
|
1167
1189
|
let newEntry = {
|
|
1168
1190
|
o: entry?.o,
|
|
@@ -1189,8 +1211,8 @@ async function handleMoveStream(request) {
|
|
|
1189
1211
|
meta[newKey] = newEntry;
|
|
1190
1212
|
moved.push(itemPath);
|
|
1191
1213
|
} else {
|
|
1192
|
-
const absolutePath =
|
|
1193
|
-
if (absoluteDestination.startsWith(absolutePath +
|
|
1214
|
+
const absolutePath = getWorkspacePath(safePath);
|
|
1215
|
+
if (absoluteDestination.startsWith(absolutePath + path5.sep)) {
|
|
1194
1216
|
errors.push(`Cannot move ${itemName} into itself`);
|
|
1195
1217
|
continue;
|
|
1196
1218
|
}
|
|
@@ -1212,9 +1234,9 @@ async function handleMoveStream(request) {
|
|
|
1212
1234
|
const oldThumbPaths = getAllThumbnailPaths(oldKey);
|
|
1213
1235
|
const newThumbPaths = getAllThumbnailPaths(newKey);
|
|
1214
1236
|
for (let j = 0; j < oldThumbPaths.length; j++) {
|
|
1215
|
-
const oldThumbPath =
|
|
1216
|
-
const newThumbPath =
|
|
1217
|
-
await fs5.mkdir(
|
|
1237
|
+
const oldThumbPath = getPublicPath(oldThumbPaths[j]);
|
|
1238
|
+
const newThumbPath = getPublicPath(newThumbPaths[j]);
|
|
1239
|
+
await fs5.mkdir(path5.dirname(newThumbPath), { recursive: true });
|
|
1218
1240
|
try {
|
|
1219
1241
|
await fs5.rename(oldThumbPath, newThumbPath);
|
|
1220
1242
|
} catch {
|
|
@@ -1267,7 +1289,7 @@ async function handleMoveStream(request) {
|
|
|
1267
1289
|
// src/handlers/images.ts
|
|
1268
1290
|
import { NextResponse as NextResponse3 } from "next/server";
|
|
1269
1291
|
import { promises as fs6 } from "fs";
|
|
1270
|
-
import
|
|
1292
|
+
import path6 from "path";
|
|
1271
1293
|
import { S3Client as S3Client2, PutObjectCommand as PutObjectCommand2 } from "@aws-sdk/client-s3";
|
|
1272
1294
|
async function handleSync(request) {
|
|
1273
1295
|
const accountId = process.env.CLOUDFLARE_R2_ACCOUNT_ID;
|
|
@@ -1319,7 +1341,7 @@ async function handleSync(request) {
|
|
|
1319
1341
|
const remoteUrl = `${existingCdnUrl}${imageKey}`;
|
|
1320
1342
|
originalBuffer = await downloadFromRemoteUrl(remoteUrl);
|
|
1321
1343
|
} else {
|
|
1322
|
-
const originalLocalPath =
|
|
1344
|
+
const originalLocalPath = getPublicPath(imageKey);
|
|
1323
1345
|
try {
|
|
1324
1346
|
originalBuffer = await fs6.readFile(originalLocalPath);
|
|
1325
1347
|
} catch {
|
|
@@ -1338,7 +1360,7 @@ async function handleSync(request) {
|
|
|
1338
1360
|
urlsToPurge.push(`${publicUrl}${imageKey}`);
|
|
1339
1361
|
if (!isRemote && isProcessed(entry)) {
|
|
1340
1362
|
for (const thumbPath of getAllThumbnailPaths(imageKey)) {
|
|
1341
|
-
const localPath =
|
|
1363
|
+
const localPath = getPublicPath(thumbPath);
|
|
1342
1364
|
try {
|
|
1343
1365
|
const fileBuffer = await fs6.readFile(localPath);
|
|
1344
1366
|
await r2.send(
|
|
@@ -1356,9 +1378,9 @@ async function handleSync(request) {
|
|
|
1356
1378
|
}
|
|
1357
1379
|
entry.c = cdnIndex;
|
|
1358
1380
|
if (!isRemote) {
|
|
1359
|
-
const originalLocalPath =
|
|
1381
|
+
const originalLocalPath = getPublicPath(imageKey);
|
|
1360
1382
|
for (const thumbPath of getAllThumbnailPaths(imageKey)) {
|
|
1361
|
-
const localPath =
|
|
1383
|
+
const localPath = getPublicPath(thumbPath);
|
|
1362
1384
|
try {
|
|
1363
1385
|
await fs6.unlink(localPath);
|
|
1364
1386
|
} catch {
|
|
@@ -1412,19 +1434,19 @@ async function handleReprocess(request) {
|
|
|
1412
1434
|
const existingCdnUrl = existingCdnIndex !== void 0 ? cdnUrls[existingCdnIndex] : void 0;
|
|
1413
1435
|
const isInOurR2 = existingCdnUrl === publicUrl;
|
|
1414
1436
|
const isRemote = existingCdnIndex !== void 0 && !isInOurR2;
|
|
1415
|
-
const originalPath =
|
|
1437
|
+
const originalPath = getPublicPath(imageKey);
|
|
1416
1438
|
try {
|
|
1417
1439
|
buffer = await fs6.readFile(originalPath);
|
|
1418
1440
|
} catch {
|
|
1419
1441
|
if (isInOurR2) {
|
|
1420
1442
|
buffer = await downloadFromCdn(imageKey);
|
|
1421
|
-
const dir =
|
|
1443
|
+
const dir = path6.dirname(originalPath);
|
|
1422
1444
|
await fs6.mkdir(dir, { recursive: true });
|
|
1423
1445
|
await fs6.writeFile(originalPath, buffer);
|
|
1424
1446
|
} else if (isRemote && existingCdnUrl) {
|
|
1425
1447
|
const remoteUrl = `${existingCdnUrl}${imageKey}`;
|
|
1426
1448
|
buffer = await downloadFromRemoteUrl(remoteUrl);
|
|
1427
|
-
const dir =
|
|
1449
|
+
const dir = path6.dirname(originalPath);
|
|
1428
1450
|
await fs6.mkdir(dir, { recursive: true });
|
|
1429
1451
|
await fs6.writeFile(originalPath, buffer);
|
|
1430
1452
|
} else {
|
|
@@ -1622,33 +1644,33 @@ async function handleReprocessStream(request) {
|
|
|
1622
1644
|
const existingCdnUrl = existingCdnIndex !== void 0 ? cdnUrls[existingCdnIndex] : void 0;
|
|
1623
1645
|
const isInOurR2 = existingCdnUrl === publicUrl;
|
|
1624
1646
|
const isRemote = existingCdnIndex !== void 0 && !isInOurR2;
|
|
1625
|
-
const originalPath =
|
|
1647
|
+
const originalPath = getPublicPath(imageKey);
|
|
1626
1648
|
try {
|
|
1627
1649
|
buffer = await fs6.readFile(originalPath);
|
|
1628
1650
|
} catch {
|
|
1629
1651
|
if (isInOurR2) {
|
|
1630
1652
|
buffer = await downloadFromCdn(imageKey);
|
|
1631
|
-
const dir =
|
|
1653
|
+
const dir = path6.dirname(originalPath);
|
|
1632
1654
|
await fs6.mkdir(dir, { recursive: true });
|
|
1633
1655
|
await fs6.writeFile(originalPath, buffer);
|
|
1634
1656
|
} else if (isRemote && existingCdnUrl) {
|
|
1635
1657
|
const remoteUrl = `${existingCdnUrl}${imageKey}`;
|
|
1636
1658
|
buffer = await downloadFromRemoteUrl(remoteUrl);
|
|
1637
|
-
const dir =
|
|
1659
|
+
const dir = path6.dirname(originalPath);
|
|
1638
1660
|
await fs6.mkdir(dir, { recursive: true });
|
|
1639
1661
|
await fs6.writeFile(originalPath, buffer);
|
|
1640
1662
|
} else {
|
|
1641
1663
|
throw new Error(`File not found: ${imageKey}`);
|
|
1642
1664
|
}
|
|
1643
1665
|
}
|
|
1644
|
-
const ext =
|
|
1666
|
+
const ext = path6.extname(imageKey).toLowerCase();
|
|
1645
1667
|
const isSvg = ext === ".svg";
|
|
1646
1668
|
if (isSvg) {
|
|
1647
|
-
const imageDir =
|
|
1648
|
-
const imagesPath =
|
|
1669
|
+
const imageDir = path6.dirname(imageKey.slice(1));
|
|
1670
|
+
const imagesPath = getPublicPath("images", imageDir === "." ? "" : imageDir);
|
|
1649
1671
|
await fs6.mkdir(imagesPath, { recursive: true });
|
|
1650
|
-
const fileName =
|
|
1651
|
-
const destPath =
|
|
1672
|
+
const fileName = path6.basename(imageKey);
|
|
1673
|
+
const destPath = path6.join(imagesPath, fileName);
|
|
1652
1674
|
await fs6.writeFile(destPath, buffer);
|
|
1653
1675
|
meta[imageKey] = {
|
|
1654
1676
|
...entry,
|
|
@@ -1733,7 +1755,7 @@ async function handleProcessAllStream() {
|
|
|
1733
1755
|
let alreadyProcessed = 0;
|
|
1734
1756
|
const imagesToProcess = [];
|
|
1735
1757
|
for (const [key, entry] of getFileEntries(meta)) {
|
|
1736
|
-
const fileName =
|
|
1758
|
+
const fileName = path6.basename(key);
|
|
1737
1759
|
if (!isImageFile(fileName)) continue;
|
|
1738
1760
|
if (!isProcessed(entry)) {
|
|
1739
1761
|
imagesToProcess.push({ key, entry });
|
|
@@ -1745,7 +1767,7 @@ async function handleProcessAllStream() {
|
|
|
1745
1767
|
sendEvent({ type: "start", total });
|
|
1746
1768
|
for (let i = 0; i < imagesToProcess.length; i++) {
|
|
1747
1769
|
const { key, entry } = imagesToProcess[i];
|
|
1748
|
-
const fullPath =
|
|
1770
|
+
const fullPath = getPublicPath(key);
|
|
1749
1771
|
const existingCdnIndex = entry.c;
|
|
1750
1772
|
const existingCdnUrl = existingCdnIndex !== void 0 ? cdnUrls[existingCdnIndex] : void 0;
|
|
1751
1773
|
const isInOurR2 = existingCdnUrl === publicUrl;
|
|
@@ -1762,26 +1784,26 @@ async function handleProcessAllStream() {
|
|
|
1762
1784
|
let buffer;
|
|
1763
1785
|
if (isInOurR2) {
|
|
1764
1786
|
buffer = await downloadFromCdn(key);
|
|
1765
|
-
const dir =
|
|
1787
|
+
const dir = path6.dirname(fullPath);
|
|
1766
1788
|
await fs6.mkdir(dir, { recursive: true });
|
|
1767
1789
|
await fs6.writeFile(fullPath, buffer);
|
|
1768
1790
|
} else if (isRemote && existingCdnUrl) {
|
|
1769
1791
|
const remoteUrl = `${existingCdnUrl}${key}`;
|
|
1770
1792
|
buffer = await downloadFromRemoteUrl(remoteUrl);
|
|
1771
|
-
const dir =
|
|
1793
|
+
const dir = path6.dirname(fullPath);
|
|
1772
1794
|
await fs6.mkdir(dir, { recursive: true });
|
|
1773
1795
|
await fs6.writeFile(fullPath, buffer);
|
|
1774
1796
|
} else {
|
|
1775
1797
|
buffer = await fs6.readFile(fullPath);
|
|
1776
1798
|
}
|
|
1777
|
-
const ext =
|
|
1799
|
+
const ext = path6.extname(key).toLowerCase();
|
|
1778
1800
|
const isSvg = ext === ".svg";
|
|
1779
1801
|
if (isSvg) {
|
|
1780
|
-
const imageDir =
|
|
1781
|
-
const imagesPath =
|
|
1802
|
+
const imageDir = path6.dirname(key.slice(1));
|
|
1803
|
+
const imagesPath = getPublicPath("images", imageDir === "." ? "" : imageDir);
|
|
1782
1804
|
await fs6.mkdir(imagesPath, { recursive: true });
|
|
1783
|
-
const fileName =
|
|
1784
|
-
const destPath =
|
|
1805
|
+
const fileName = path6.basename(key);
|
|
1806
|
+
const destPath = path6.join(imagesPath, fileName);
|
|
1785
1807
|
await fs6.writeFile(destPath, buffer);
|
|
1786
1808
|
meta[key] = {
|
|
1787
1809
|
...entry,
|
|
@@ -1831,7 +1853,7 @@ async function handleProcessAllStream() {
|
|
|
1831
1853
|
const entries = await fs6.readdir(dir, { withFileTypes: true });
|
|
1832
1854
|
for (const fsEntry of entries) {
|
|
1833
1855
|
if (fsEntry.name.startsWith(".")) continue;
|
|
1834
|
-
const entryFullPath =
|
|
1856
|
+
const entryFullPath = path6.join(dir, fsEntry.name);
|
|
1835
1857
|
const relPath = relativePath ? `${relativePath}/${fsEntry.name}` : fsEntry.name;
|
|
1836
1858
|
if (fsEntry.isDirectory()) {
|
|
1837
1859
|
await findOrphans(entryFullPath, relPath);
|
|
@@ -1850,7 +1872,7 @@ async function handleProcessAllStream() {
|
|
|
1850
1872
|
} catch {
|
|
1851
1873
|
}
|
|
1852
1874
|
}
|
|
1853
|
-
const imagesDir =
|
|
1875
|
+
const imagesDir = getPublicPath("images");
|
|
1854
1876
|
try {
|
|
1855
1877
|
await findOrphans(imagesDir);
|
|
1856
1878
|
} catch {
|
|
@@ -1861,7 +1883,7 @@ async function handleProcessAllStream() {
|
|
|
1861
1883
|
let isEmpty = true;
|
|
1862
1884
|
for (const fsEntry of entries) {
|
|
1863
1885
|
if (fsEntry.isDirectory()) {
|
|
1864
|
-
const subDirEmpty = await removeEmptyDirs(
|
|
1886
|
+
const subDirEmpty = await removeEmptyDirs(path6.join(dir, fsEntry.name));
|
|
1865
1887
|
if (!subDirEmpty) isEmpty = false;
|
|
1866
1888
|
} else {
|
|
1867
1889
|
isEmpty = false;
|
|
@@ -1940,8 +1962,8 @@ async function handleDownloadStream(request) {
|
|
|
1940
1962
|
}
|
|
1941
1963
|
try {
|
|
1942
1964
|
const imageBuffer = await downloadFromCdn(imageKey);
|
|
1943
|
-
const localPath =
|
|
1944
|
-
await fs6.mkdir(
|
|
1965
|
+
const localPath = getPublicPath(imageKey.replace(/^\//, ""));
|
|
1966
|
+
await fs6.mkdir(path6.dirname(localPath), { recursive: true });
|
|
1945
1967
|
await fs6.writeFile(localPath, imageBuffer);
|
|
1946
1968
|
await deleteThumbnailsFromCdn(imageKey);
|
|
1947
1969
|
const wasProcessed = isProcessed(entry);
|
|
@@ -2006,7 +2028,7 @@ async function handleDownloadStream(request) {
|
|
|
2006
2028
|
// src/handlers/scan.ts
|
|
2007
2029
|
import { NextResponse as NextResponse4 } from "next/server";
|
|
2008
2030
|
import { promises as fs7 } from "fs";
|
|
2009
|
-
import
|
|
2031
|
+
import path7 from "path";
|
|
2010
2032
|
import sharp3 from "sharp";
|
|
2011
2033
|
import { encode as encode2 } from "blurhash";
|
|
2012
2034
|
async function handleScanStream() {
|
|
@@ -2032,7 +2054,7 @@ async function handleScanStream() {
|
|
|
2032
2054
|
const entries = await fs7.readdir(dir, { withFileTypes: true });
|
|
2033
2055
|
for (const entry of entries) {
|
|
2034
2056
|
if (entry.name.startsWith(".")) continue;
|
|
2035
|
-
const fullPath =
|
|
2057
|
+
const fullPath = path7.join(dir, entry.name);
|
|
2036
2058
|
const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
2037
2059
|
if (relPath === "images" || relPath.startsWith("images/")) continue;
|
|
2038
2060
|
if (entry.isDirectory()) {
|
|
@@ -2044,7 +2066,7 @@ async function handleScanStream() {
|
|
|
2044
2066
|
} catch {
|
|
2045
2067
|
}
|
|
2046
2068
|
}
|
|
2047
|
-
const publicDir =
|
|
2069
|
+
const publicDir = getPublicPath();
|
|
2048
2070
|
await scanDir(publicDir);
|
|
2049
2071
|
const total = allFiles.length;
|
|
2050
2072
|
sendEvent({ type: "start", total });
|
|
@@ -2062,7 +2084,7 @@ async function handleScanStream() {
|
|
|
2062
2084
|
continue;
|
|
2063
2085
|
}
|
|
2064
2086
|
if (meta[imageKey]) {
|
|
2065
|
-
const ext =
|
|
2087
|
+
const ext = path7.extname(relativePath);
|
|
2066
2088
|
const baseName = relativePath.slice(0, -ext.length);
|
|
2067
2089
|
let counter = 1;
|
|
2068
2090
|
let newKey = `/${baseName}-${counter}${ext}`;
|
|
@@ -2071,7 +2093,7 @@ async function handleScanStream() {
|
|
|
2071
2093
|
newKey = `/${baseName}-${counter}${ext}`;
|
|
2072
2094
|
}
|
|
2073
2095
|
const newRelativePath = `${baseName}-${counter}${ext}`;
|
|
2074
|
-
const newFullPath =
|
|
2096
|
+
const newFullPath = getPublicPath(newRelativePath);
|
|
2075
2097
|
try {
|
|
2076
2098
|
await fs7.rename(fullPath, newFullPath);
|
|
2077
2099
|
renamed.push({ from: relativePath, to: newRelativePath });
|
|
@@ -2087,7 +2109,7 @@ async function handleScanStream() {
|
|
|
2087
2109
|
try {
|
|
2088
2110
|
const isImage = isImageFile(relativePath);
|
|
2089
2111
|
if (isImage) {
|
|
2090
|
-
const ext =
|
|
2112
|
+
const ext = path7.extname(relativePath).toLowerCase();
|
|
2091
2113
|
if (ext === ".svg") {
|
|
2092
2114
|
meta[imageKey] = { o: { w: 0, h: 0 }, b: "" };
|
|
2093
2115
|
} else {
|
|
@@ -2129,7 +2151,7 @@ async function handleScanStream() {
|
|
|
2129
2151
|
const entries = await fs7.readdir(dir, { withFileTypes: true });
|
|
2130
2152
|
for (const entry of entries) {
|
|
2131
2153
|
if (entry.name.startsWith(".")) continue;
|
|
2132
|
-
const fullPath =
|
|
2154
|
+
const fullPath = path7.join(dir, entry.name);
|
|
2133
2155
|
const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
2134
2156
|
if (entry.isDirectory()) {
|
|
2135
2157
|
await findOrphans(fullPath, relPath);
|
|
@@ -2143,7 +2165,7 @@ async function handleScanStream() {
|
|
|
2143
2165
|
} catch {
|
|
2144
2166
|
}
|
|
2145
2167
|
}
|
|
2146
|
-
const imagesDir =
|
|
2168
|
+
const imagesDir = getPublicPath("images");
|
|
2147
2169
|
try {
|
|
2148
2170
|
await findOrphans(imagesDir);
|
|
2149
2171
|
} catch {
|
|
@@ -2187,7 +2209,7 @@ async function handleDeleteOrphans(request) {
|
|
|
2187
2209
|
errors.push(`Invalid path: ${orphanPath}`);
|
|
2188
2210
|
continue;
|
|
2189
2211
|
}
|
|
2190
|
-
const fullPath =
|
|
2212
|
+
const fullPath = getPublicPath(orphanPath);
|
|
2191
2213
|
try {
|
|
2192
2214
|
await fs7.unlink(fullPath);
|
|
2193
2215
|
deleted.push(orphanPath);
|
|
@@ -2196,14 +2218,14 @@ async function handleDeleteOrphans(request) {
|
|
|
2196
2218
|
errors.push(orphanPath);
|
|
2197
2219
|
}
|
|
2198
2220
|
}
|
|
2199
|
-
const imagesDir =
|
|
2221
|
+
const imagesDir = getPublicPath("images");
|
|
2200
2222
|
async function removeEmptyDirs(dir) {
|
|
2201
2223
|
try {
|
|
2202
2224
|
const entries = await fs7.readdir(dir, { withFileTypes: true });
|
|
2203
2225
|
let isEmpty = true;
|
|
2204
2226
|
for (const entry of entries) {
|
|
2205
2227
|
if (entry.isDirectory()) {
|
|
2206
|
-
const subDirEmpty = await removeEmptyDirs(
|
|
2228
|
+
const subDirEmpty = await removeEmptyDirs(path7.join(dir, entry.name));
|
|
2207
2229
|
if (!subDirEmpty) isEmpty = false;
|
|
2208
2230
|
} else {
|
|
2209
2231
|
isEmpty = false;
|
|
@@ -2238,8 +2260,8 @@ import { encode as encode3 } from "blurhash";
|
|
|
2238
2260
|
function parseImageUrl(url) {
|
|
2239
2261
|
const parsed = new URL(url);
|
|
2240
2262
|
const base = `${parsed.protocol}//${parsed.host}`;
|
|
2241
|
-
const
|
|
2242
|
-
return { base, path:
|
|
2263
|
+
const path9 = parsed.pathname;
|
|
2264
|
+
return { base, path: path9 };
|
|
2243
2265
|
}
|
|
2244
2266
|
async function processRemoteImage(url) {
|
|
2245
2267
|
const response = await fetch(url);
|
|
@@ -2288,20 +2310,20 @@ async function handleImportUrls(request) {
|
|
|
2288
2310
|
currentFile: url
|
|
2289
2311
|
});
|
|
2290
2312
|
try {
|
|
2291
|
-
const { base, path:
|
|
2292
|
-
const existingEntry = getMetaEntry(meta,
|
|
2313
|
+
const { base, path: path9 } = parseImageUrl(url);
|
|
2314
|
+
const existingEntry = getMetaEntry(meta, path9);
|
|
2293
2315
|
if (existingEntry) {
|
|
2294
|
-
skipped.push(
|
|
2316
|
+
skipped.push(path9);
|
|
2295
2317
|
continue;
|
|
2296
2318
|
}
|
|
2297
2319
|
const cdnIndex = getOrAddCdnIndex(meta, base);
|
|
2298
2320
|
const imageData = await processRemoteImage(url);
|
|
2299
|
-
setMetaEntry(meta,
|
|
2321
|
+
setMetaEntry(meta, path9, {
|
|
2300
2322
|
o: imageData.o,
|
|
2301
2323
|
b: imageData.b,
|
|
2302
2324
|
c: cdnIndex
|
|
2303
2325
|
});
|
|
2304
|
-
added.push(
|
|
2326
|
+
added.push(path9);
|
|
2305
2327
|
} catch (error) {
|
|
2306
2328
|
console.error(`Failed to import ${url}:`, error);
|
|
2307
2329
|
errors.push(url);
|
|
@@ -2359,7 +2381,7 @@ async function handleUpdateCdns(request) {
|
|
|
2359
2381
|
// src/handlers/favicon.ts
|
|
2360
2382
|
import { NextResponse as NextResponse5 } from "next/server";
|
|
2361
2383
|
import sharp5 from "sharp";
|
|
2362
|
-
import
|
|
2384
|
+
import path8 from "path";
|
|
2363
2385
|
import fs8 from "fs/promises";
|
|
2364
2386
|
var FAVICON_CONFIGS = [
|
|
2365
2387
|
{ name: "favicon.ico", size: 48 },
|
|
@@ -2378,13 +2400,13 @@ async function handleGenerateFavicon(request) {
|
|
|
2378
2400
|
} catch {
|
|
2379
2401
|
return NextResponse5.json({ error: "Invalid request body" }, { status: 400 });
|
|
2380
2402
|
}
|
|
2381
|
-
const fileName =
|
|
2403
|
+
const fileName = path8.basename(imagePath).toLowerCase();
|
|
2382
2404
|
if (fileName !== "favicon.png" && fileName !== "favicon.jpg") {
|
|
2383
2405
|
return NextResponse5.json({
|
|
2384
2406
|
error: "Source file must be named favicon.png or favicon.jpg"
|
|
2385
2407
|
}, { status: 400 });
|
|
2386
2408
|
}
|
|
2387
|
-
const sourcePath =
|
|
2409
|
+
const sourcePath = getPublicPath(imagePath.replace(/^\//, ""));
|
|
2388
2410
|
try {
|
|
2389
2411
|
await fs8.access(sourcePath);
|
|
2390
2412
|
} catch {
|
|
@@ -2396,7 +2418,7 @@ async function handleGenerateFavicon(request) {
|
|
|
2396
2418
|
} catch {
|
|
2397
2419
|
return NextResponse5.json({ error: "Source file is not a valid image" }, { status: 400 });
|
|
2398
2420
|
}
|
|
2399
|
-
const outputDir =
|
|
2421
|
+
const outputDir = getSrcAppPath();
|
|
2400
2422
|
try {
|
|
2401
2423
|
await fs8.access(outputDir);
|
|
2402
2424
|
} catch {
|
|
@@ -2430,7 +2452,7 @@ async function handleGenerateFavicon(request) {
|
|
|
2430
2452
|
message: `Generating ${config.name} (${config.size}x${config.size})...`
|
|
2431
2453
|
});
|
|
2432
2454
|
try {
|
|
2433
|
-
const outputPath =
|
|
2455
|
+
const outputPath = path8.join(outputDir, config.name);
|
|
2434
2456
|
await sharp5(sourcePath).resize(config.size, config.size, {
|
|
2435
2457
|
fit: "cover",
|
|
2436
2458
|
position: "center"
|