@gallop.software/studio 1.5.9 → 2.0.0
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/app/api/studio/[...path]/route.ts +1 -0
- package/app/layout.tsx +20 -0
- package/app/page.tsx +82 -0
- package/bin/studio.mjs +110 -0
- package/dist/handlers/index.js +84 -63
- package/dist/handlers/index.js.map +1 -1
- package/dist/handlers/index.mjs +135 -114
- package/dist/handlers/index.mjs.map +1 -1
- package/dist/index.d.mts +14 -10
- package/dist/index.d.ts +14 -10
- package/dist/index.js +2 -177
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4 -179
- package/dist/index.mjs.map +1 -1
- package/next.config.mjs +22 -0
- package/package.json +18 -10
- package/src/components/AddNewModal.tsx +402 -0
- package/src/components/ErrorModal.tsx +89 -0
- package/src/components/R2SetupModal.tsx +400 -0
- package/src/components/StudioBreadcrumb.tsx +115 -0
- package/src/components/StudioButton.tsx +200 -0
- package/src/components/StudioContext.tsx +219 -0
- package/src/components/StudioDetailView.tsx +714 -0
- package/src/components/StudioFileGrid.tsx +704 -0
- package/src/components/StudioFileList.tsx +743 -0
- package/src/components/StudioFolderPicker.tsx +342 -0
- package/src/components/StudioModal.tsx +473 -0
- package/src/components/StudioPreview.tsx +399 -0
- package/src/components/StudioSettings.tsx +536 -0
- package/src/components/StudioToolbar.tsx +1448 -0
- package/src/components/StudioUI.tsx +731 -0
- package/src/components/styles/common.ts +236 -0
- package/src/components/tokens.ts +78 -0
- package/src/components/useStudioActions.tsx +497 -0
- package/src/config/index.ts +7 -0
- package/src/config/workspace.ts +52 -0
- package/src/handlers/favicon.ts +152 -0
- package/src/handlers/files.ts +784 -0
- package/src/handlers/images.ts +949 -0
- package/src/handlers/import.ts +190 -0
- package/src/handlers/index.ts +168 -0
- package/src/handlers/list.ts +627 -0
- package/src/handlers/scan.ts +311 -0
- package/src/handlers/utils/cdn.ts +234 -0
- package/src/handlers/utils/files.ts +64 -0
- package/src/handlers/utils/index.ts +4 -0
- package/src/handlers/utils/meta.ts +102 -0
- package/src/handlers/utils/thumbnails.ts +98 -0
- package/src/hooks/useFileList.ts +143 -0
- package/src/index.tsx +36 -0
- package/src/lib/api.ts +176 -0
- package/src/types.ts +119 -0
- package/dist/StudioUI-GJK45R3T.js +0 -6500
- package/dist/StudioUI-GJK45R3T.js.map +0 -1
- package/dist/StudioUI-QZ54STXE.mjs +0 -6500
- package/dist/StudioUI-QZ54STXE.mjs.map +0 -1
- package/dist/chunk-N6JYTJCB.js +0 -68
- package/dist/chunk-N6JYTJCB.js.map +0 -1
- package/dist/chunk-RHI3UROE.mjs +0 -68
- package/dist/chunk-RHI3UROE.mjs.map +0 -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
|
}
|
|
@@ -801,35 +823,34 @@ async function handleFolderImages(request) {
|
|
|
801
823
|
const folders = foldersParam.split(",");
|
|
802
824
|
const meta = await loadMeta();
|
|
803
825
|
const fileEntries = getFileEntries(meta);
|
|
804
|
-
const
|
|
826
|
+
const allFiles = [];
|
|
805
827
|
const prefixes = folders.map((f) => {
|
|
806
828
|
const rel = f.replace(/^public\/?/, "");
|
|
807
829
|
return rel ? `/${rel}/` : "/";
|
|
808
830
|
});
|
|
809
831
|
for (const [key] of fileEntries) {
|
|
810
|
-
const fileName = path5.basename(key);
|
|
811
|
-
if (!isImageFile(fileName)) continue;
|
|
812
832
|
for (const prefix of prefixes) {
|
|
813
833
|
if (key.startsWith(prefix) || prefix === "/" && key.startsWith("/")) {
|
|
814
|
-
|
|
834
|
+
allFiles.push(key.slice(1));
|
|
815
835
|
break;
|
|
816
836
|
}
|
|
817
837
|
}
|
|
818
838
|
}
|
|
819
839
|
return NextResponse.json({
|
|
820
|
-
count:
|
|
821
|
-
images:
|
|
840
|
+
count: allFiles.length,
|
|
841
|
+
images: allFiles
|
|
842
|
+
// Keep as 'images' for backwards compatibility
|
|
822
843
|
});
|
|
823
844
|
} catch (error) {
|
|
824
|
-
console.error("Failed to get folder
|
|
825
|
-
return NextResponse.json({ error: "Failed to get folder
|
|
845
|
+
console.error("Failed to get folder files:", error);
|
|
846
|
+
return NextResponse.json({ error: "Failed to get folder files" }, { status: 500 });
|
|
826
847
|
}
|
|
827
848
|
}
|
|
828
849
|
|
|
829
850
|
// src/handlers/files.ts
|
|
830
851
|
import { NextResponse as NextResponse2 } from "next/server";
|
|
831
852
|
import { promises as fs5 } from "fs";
|
|
832
|
-
import
|
|
853
|
+
import path5 from "path";
|
|
833
854
|
import sharp2 from "sharp";
|
|
834
855
|
async function handleUpload(request) {
|
|
835
856
|
try {
|
|
@@ -842,7 +863,7 @@ async function handleUpload(request) {
|
|
|
842
863
|
const bytes = await file.arrayBuffer();
|
|
843
864
|
const buffer = Buffer.from(bytes);
|
|
844
865
|
const fileName = file.name;
|
|
845
|
-
const ext =
|
|
866
|
+
const ext = path5.extname(fileName).toLowerCase();
|
|
846
867
|
const isImage = isImageFile(fileName);
|
|
847
868
|
const isMedia = isMediaFile(fileName);
|
|
848
869
|
const meta = await loadMeta();
|
|
@@ -860,7 +881,7 @@ async function handleUpload(request) {
|
|
|
860
881
|
}
|
|
861
882
|
let imageKey = "/" + (relativeDir ? `${relativeDir}/${fileName}` : fileName);
|
|
862
883
|
if (meta[imageKey]) {
|
|
863
|
-
const baseName =
|
|
884
|
+
const baseName = path5.basename(fileName, ext);
|
|
864
885
|
let counter = 1;
|
|
865
886
|
let newFileName = `${baseName}-${counter}${ext}`;
|
|
866
887
|
let newKey = "/" + (relativeDir ? `${relativeDir}/${newFileName}` : newFileName);
|
|
@@ -871,10 +892,10 @@ async function handleUpload(request) {
|
|
|
871
892
|
}
|
|
872
893
|
imageKey = newKey;
|
|
873
894
|
}
|
|
874
|
-
const actualFileName =
|
|
875
|
-
const uploadDir =
|
|
895
|
+
const actualFileName = path5.basename(imageKey);
|
|
896
|
+
const uploadDir = getPublicPath(relativeDir);
|
|
876
897
|
await fs5.mkdir(uploadDir, { recursive: true });
|
|
877
|
-
await fs5.writeFile(
|
|
898
|
+
await fs5.writeFile(path5.join(uploadDir, actualFileName), buffer);
|
|
878
899
|
if (!isMedia) {
|
|
879
900
|
return NextResponse2.json({
|
|
880
901
|
success: true,
|
|
@@ -921,7 +942,7 @@ async function handleDelete(request) {
|
|
|
921
942
|
errors.push(`Invalid path: ${itemPath}`);
|
|
922
943
|
continue;
|
|
923
944
|
}
|
|
924
|
-
const absolutePath =
|
|
945
|
+
const absolutePath = getWorkspacePath(itemPath);
|
|
925
946
|
const imageKey = "/" + itemPath.replace(/^public\//, "");
|
|
926
947
|
const entry = meta[imageKey];
|
|
927
948
|
const isPushedToCloud = entry?.c !== void 0;
|
|
@@ -935,7 +956,7 @@ async function handleDelete(request) {
|
|
|
935
956
|
const keyEntry = meta[key];
|
|
936
957
|
if (keyEntry && keyEntry.c === void 0) {
|
|
937
958
|
for (const thumbPath of getAllThumbnailPaths(key)) {
|
|
938
|
-
const absoluteThumbPath =
|
|
959
|
+
const absoluteThumbPath = getPublicPath(thumbPath);
|
|
939
960
|
try {
|
|
940
961
|
await fs5.unlink(absoluteThumbPath);
|
|
941
962
|
} catch {
|
|
@@ -951,7 +972,7 @@ async function handleDelete(request) {
|
|
|
951
972
|
if (!isInImagesFolder && entry) {
|
|
952
973
|
if (!isPushedToCloud) {
|
|
953
974
|
for (const thumbPath of getAllThumbnailPaths(imageKey)) {
|
|
954
|
-
const absoluteThumbPath =
|
|
975
|
+
const absoluteThumbPath = getPublicPath(thumbPath);
|
|
955
976
|
try {
|
|
956
977
|
await fs5.unlink(absoluteThumbPath);
|
|
957
978
|
} catch {
|
|
@@ -1007,8 +1028,8 @@ async function handleCreateFolder(request) {
|
|
|
1007
1028
|
return NextResponse2.json({ error: "Invalid folder name" }, { status: 400 });
|
|
1008
1029
|
}
|
|
1009
1030
|
const safePath = (parentPath || "public").replace(/\.\./g, "");
|
|
1010
|
-
const folderPath =
|
|
1011
|
-
if (!folderPath.startsWith(
|
|
1031
|
+
const folderPath = getWorkspacePath(safePath, sanitizedName);
|
|
1032
|
+
if (!folderPath.startsWith(getPublicPath())) {
|
|
1012
1033
|
return NextResponse2.json({ error: "Invalid path" }, { status: 400 });
|
|
1013
1034
|
}
|
|
1014
1035
|
try {
|
|
@@ -1017,7 +1038,7 @@ async function handleCreateFolder(request) {
|
|
|
1017
1038
|
} catch {
|
|
1018
1039
|
}
|
|
1019
1040
|
await fs5.mkdir(folderPath, { recursive: true });
|
|
1020
|
-
return NextResponse2.json({ success: true, path:
|
|
1041
|
+
return NextResponse2.json({ success: true, path: path5.join(safePath, sanitizedName) });
|
|
1021
1042
|
} catch (error) {
|
|
1022
1043
|
console.error("Failed to create folder:", error);
|
|
1023
1044
|
return NextResponse2.json({ error: "Failed to create folder" }, { status: 500 });
|
|
@@ -1034,10 +1055,10 @@ async function handleRename(request) {
|
|
|
1034
1055
|
return NextResponse2.json({ error: "Invalid name" }, { status: 400 });
|
|
1035
1056
|
}
|
|
1036
1057
|
const safePath = oldPath.replace(/\.\./g, "");
|
|
1037
|
-
const absoluteOldPath =
|
|
1038
|
-
const parentDir =
|
|
1039
|
-
const absoluteNewPath =
|
|
1040
|
-
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())) {
|
|
1041
1062
|
return NextResponse2.json({ error: "Invalid path" }, { status: 400 });
|
|
1042
1063
|
}
|
|
1043
1064
|
try {
|
|
@@ -1052,12 +1073,12 @@ async function handleRename(request) {
|
|
|
1052
1073
|
}
|
|
1053
1074
|
const stats = await fs5.stat(absoluteOldPath);
|
|
1054
1075
|
const isFile = stats.isFile();
|
|
1055
|
-
const isImage = isFile && isImageFile(
|
|
1076
|
+
const isImage = isFile && isImageFile(path5.basename(oldPath));
|
|
1056
1077
|
await fs5.rename(absoluteOldPath, absoluteNewPath);
|
|
1057
1078
|
if (isImage) {
|
|
1058
1079
|
const meta = await loadMeta();
|
|
1059
1080
|
const oldRelativePath = safePath.replace(/^public\//, "");
|
|
1060
|
-
const newRelativePath =
|
|
1081
|
+
const newRelativePath = path5.join(path5.dirname(oldRelativePath), sanitizedName);
|
|
1061
1082
|
const oldKey = "/" + oldRelativePath;
|
|
1062
1083
|
const newKey = "/" + newRelativePath;
|
|
1063
1084
|
if (meta[oldKey]) {
|
|
@@ -1065,9 +1086,9 @@ async function handleRename(request) {
|
|
|
1065
1086
|
const oldThumbPaths = getAllThumbnailPaths(oldKey);
|
|
1066
1087
|
const newThumbPaths = getAllThumbnailPaths(newKey);
|
|
1067
1088
|
for (let i = 0; i < oldThumbPaths.length; i++) {
|
|
1068
|
-
const oldThumbPath =
|
|
1069
|
-
const newThumbPath =
|
|
1070
|
-
await fs5.mkdir(
|
|
1089
|
+
const oldThumbPath = getPublicPath(oldThumbPaths[i]);
|
|
1090
|
+
const newThumbPath = getPublicPath(newThumbPaths[i]);
|
|
1091
|
+
await fs5.mkdir(path5.dirname(newThumbPath), { recursive: true });
|
|
1071
1092
|
try {
|
|
1072
1093
|
await fs5.rename(oldThumbPath, newThumbPath);
|
|
1073
1094
|
} catch {
|
|
@@ -1078,7 +1099,7 @@ async function handleRename(request) {
|
|
|
1078
1099
|
}
|
|
1079
1100
|
await saveMeta(meta);
|
|
1080
1101
|
}
|
|
1081
|
-
const newPath =
|
|
1102
|
+
const newPath = path5.join(path5.dirname(safePath), sanitizedName);
|
|
1082
1103
|
return NextResponse2.json({ success: true, newPath });
|
|
1083
1104
|
} catch (error) {
|
|
1084
1105
|
console.error("Failed to rename:", error);
|
|
@@ -1107,8 +1128,8 @@ async function handleMoveStream(request) {
|
|
|
1107
1128
|
return;
|
|
1108
1129
|
}
|
|
1109
1130
|
const safeDestination = destination.replace(/\.\./g, "");
|
|
1110
|
-
const absoluteDestination =
|
|
1111
|
-
if (!absoluteDestination.startsWith(
|
|
1131
|
+
const absoluteDestination = getWorkspacePath(safeDestination);
|
|
1132
|
+
if (!absoluteDestination.startsWith(getPublicPath())) {
|
|
1112
1133
|
sendEvent({ type: "error", message: "Invalid destination" });
|
|
1113
1134
|
controller.close();
|
|
1114
1135
|
return;
|
|
@@ -1124,10 +1145,10 @@ async function handleMoveStream(request) {
|
|
|
1124
1145
|
for (let i = 0; i < paths.length; i++) {
|
|
1125
1146
|
const itemPath = paths[i];
|
|
1126
1147
|
const safePath = itemPath.replace(/\.\./g, "");
|
|
1127
|
-
const itemName =
|
|
1128
|
-
const newAbsolutePath =
|
|
1148
|
+
const itemName = path5.basename(safePath);
|
|
1149
|
+
const newAbsolutePath = path5.join(absoluteDestination, itemName);
|
|
1129
1150
|
const oldRelativePath = safePath.replace(/^public\//, "");
|
|
1130
|
-
const newRelativePath =
|
|
1151
|
+
const newRelativePath = path5.join(safeDestination.replace(/^public\//, ""), itemName);
|
|
1131
1152
|
const oldKey = "/" + oldRelativePath;
|
|
1132
1153
|
const newKey = "/" + newRelativePath;
|
|
1133
1154
|
sendEvent({
|
|
@@ -1152,7 +1173,7 @@ async function handleMoveStream(request) {
|
|
|
1152
1173
|
if (isRemote && isImage) {
|
|
1153
1174
|
const remoteUrl = `${fileCdnUrl}${oldKey}`;
|
|
1154
1175
|
const buffer = await downloadFromRemoteUrl(remoteUrl);
|
|
1155
|
-
await fs5.mkdir(
|
|
1176
|
+
await fs5.mkdir(path5.dirname(newAbsolutePath), { recursive: true });
|
|
1156
1177
|
await fs5.writeFile(newAbsolutePath, buffer);
|
|
1157
1178
|
const newEntry = {
|
|
1158
1179
|
o: entry?.o,
|
|
@@ -1163,7 +1184,7 @@ async function handleMoveStream(request) {
|
|
|
1163
1184
|
moved.push(itemPath);
|
|
1164
1185
|
} else if (isPushedToR2 && isImage) {
|
|
1165
1186
|
const buffer = await downloadFromCdn(oldKey);
|
|
1166
|
-
await fs5.mkdir(
|
|
1187
|
+
await fs5.mkdir(path5.dirname(newAbsolutePath), { recursive: true });
|
|
1167
1188
|
await fs5.writeFile(newAbsolutePath, buffer);
|
|
1168
1189
|
let newEntry = {
|
|
1169
1190
|
o: entry?.o,
|
|
@@ -1190,8 +1211,8 @@ async function handleMoveStream(request) {
|
|
|
1190
1211
|
meta[newKey] = newEntry;
|
|
1191
1212
|
moved.push(itemPath);
|
|
1192
1213
|
} else {
|
|
1193
|
-
const absolutePath =
|
|
1194
|
-
if (absoluteDestination.startsWith(absolutePath +
|
|
1214
|
+
const absolutePath = getWorkspacePath(safePath);
|
|
1215
|
+
if (absoluteDestination.startsWith(absolutePath + path5.sep)) {
|
|
1195
1216
|
errors.push(`Cannot move ${itemName} into itself`);
|
|
1196
1217
|
continue;
|
|
1197
1218
|
}
|
|
@@ -1213,9 +1234,9 @@ async function handleMoveStream(request) {
|
|
|
1213
1234
|
const oldThumbPaths = getAllThumbnailPaths(oldKey);
|
|
1214
1235
|
const newThumbPaths = getAllThumbnailPaths(newKey);
|
|
1215
1236
|
for (let j = 0; j < oldThumbPaths.length; j++) {
|
|
1216
|
-
const oldThumbPath =
|
|
1217
|
-
const newThumbPath =
|
|
1218
|
-
await fs5.mkdir(
|
|
1237
|
+
const oldThumbPath = getPublicPath(oldThumbPaths[j]);
|
|
1238
|
+
const newThumbPath = getPublicPath(newThumbPaths[j]);
|
|
1239
|
+
await fs5.mkdir(path5.dirname(newThumbPath), { recursive: true });
|
|
1219
1240
|
try {
|
|
1220
1241
|
await fs5.rename(oldThumbPath, newThumbPath);
|
|
1221
1242
|
} catch {
|
|
@@ -1268,7 +1289,7 @@ async function handleMoveStream(request) {
|
|
|
1268
1289
|
// src/handlers/images.ts
|
|
1269
1290
|
import { NextResponse as NextResponse3 } from "next/server";
|
|
1270
1291
|
import { promises as fs6 } from "fs";
|
|
1271
|
-
import
|
|
1292
|
+
import path6 from "path";
|
|
1272
1293
|
import { S3Client as S3Client2, PutObjectCommand as PutObjectCommand2 } from "@aws-sdk/client-s3";
|
|
1273
1294
|
async function handleSync(request) {
|
|
1274
1295
|
const accountId = process.env.CLOUDFLARE_R2_ACCOUNT_ID;
|
|
@@ -1320,7 +1341,7 @@ async function handleSync(request) {
|
|
|
1320
1341
|
const remoteUrl = `${existingCdnUrl}${imageKey}`;
|
|
1321
1342
|
originalBuffer = await downloadFromRemoteUrl(remoteUrl);
|
|
1322
1343
|
} else {
|
|
1323
|
-
const originalLocalPath =
|
|
1344
|
+
const originalLocalPath = getPublicPath(imageKey);
|
|
1324
1345
|
try {
|
|
1325
1346
|
originalBuffer = await fs6.readFile(originalLocalPath);
|
|
1326
1347
|
} catch {
|
|
@@ -1339,7 +1360,7 @@ async function handleSync(request) {
|
|
|
1339
1360
|
urlsToPurge.push(`${publicUrl}${imageKey}`);
|
|
1340
1361
|
if (!isRemote && isProcessed(entry)) {
|
|
1341
1362
|
for (const thumbPath of getAllThumbnailPaths(imageKey)) {
|
|
1342
|
-
const localPath =
|
|
1363
|
+
const localPath = getPublicPath(thumbPath);
|
|
1343
1364
|
try {
|
|
1344
1365
|
const fileBuffer = await fs6.readFile(localPath);
|
|
1345
1366
|
await r2.send(
|
|
@@ -1357,9 +1378,9 @@ async function handleSync(request) {
|
|
|
1357
1378
|
}
|
|
1358
1379
|
entry.c = cdnIndex;
|
|
1359
1380
|
if (!isRemote) {
|
|
1360
|
-
const originalLocalPath =
|
|
1381
|
+
const originalLocalPath = getPublicPath(imageKey);
|
|
1361
1382
|
for (const thumbPath of getAllThumbnailPaths(imageKey)) {
|
|
1362
|
-
const localPath =
|
|
1383
|
+
const localPath = getPublicPath(thumbPath);
|
|
1363
1384
|
try {
|
|
1364
1385
|
await fs6.unlink(localPath);
|
|
1365
1386
|
} catch {
|
|
@@ -1413,19 +1434,19 @@ async function handleReprocess(request) {
|
|
|
1413
1434
|
const existingCdnUrl = existingCdnIndex !== void 0 ? cdnUrls[existingCdnIndex] : void 0;
|
|
1414
1435
|
const isInOurR2 = existingCdnUrl === publicUrl;
|
|
1415
1436
|
const isRemote = existingCdnIndex !== void 0 && !isInOurR2;
|
|
1416
|
-
const originalPath =
|
|
1437
|
+
const originalPath = getPublicPath(imageKey);
|
|
1417
1438
|
try {
|
|
1418
1439
|
buffer = await fs6.readFile(originalPath);
|
|
1419
1440
|
} catch {
|
|
1420
1441
|
if (isInOurR2) {
|
|
1421
1442
|
buffer = await downloadFromCdn(imageKey);
|
|
1422
|
-
const dir =
|
|
1443
|
+
const dir = path6.dirname(originalPath);
|
|
1423
1444
|
await fs6.mkdir(dir, { recursive: true });
|
|
1424
1445
|
await fs6.writeFile(originalPath, buffer);
|
|
1425
1446
|
} else if (isRemote && existingCdnUrl) {
|
|
1426
1447
|
const remoteUrl = `${existingCdnUrl}${imageKey}`;
|
|
1427
1448
|
buffer = await downloadFromRemoteUrl(remoteUrl);
|
|
1428
|
-
const dir =
|
|
1449
|
+
const dir = path6.dirname(originalPath);
|
|
1429
1450
|
await fs6.mkdir(dir, { recursive: true });
|
|
1430
1451
|
await fs6.writeFile(originalPath, buffer);
|
|
1431
1452
|
} else {
|
|
@@ -1623,33 +1644,33 @@ async function handleReprocessStream(request) {
|
|
|
1623
1644
|
const existingCdnUrl = existingCdnIndex !== void 0 ? cdnUrls[existingCdnIndex] : void 0;
|
|
1624
1645
|
const isInOurR2 = existingCdnUrl === publicUrl;
|
|
1625
1646
|
const isRemote = existingCdnIndex !== void 0 && !isInOurR2;
|
|
1626
|
-
const originalPath =
|
|
1647
|
+
const originalPath = getPublicPath(imageKey);
|
|
1627
1648
|
try {
|
|
1628
1649
|
buffer = await fs6.readFile(originalPath);
|
|
1629
1650
|
} catch {
|
|
1630
1651
|
if (isInOurR2) {
|
|
1631
1652
|
buffer = await downloadFromCdn(imageKey);
|
|
1632
|
-
const dir =
|
|
1653
|
+
const dir = path6.dirname(originalPath);
|
|
1633
1654
|
await fs6.mkdir(dir, { recursive: true });
|
|
1634
1655
|
await fs6.writeFile(originalPath, buffer);
|
|
1635
1656
|
} else if (isRemote && existingCdnUrl) {
|
|
1636
1657
|
const remoteUrl = `${existingCdnUrl}${imageKey}`;
|
|
1637
1658
|
buffer = await downloadFromRemoteUrl(remoteUrl);
|
|
1638
|
-
const dir =
|
|
1659
|
+
const dir = path6.dirname(originalPath);
|
|
1639
1660
|
await fs6.mkdir(dir, { recursive: true });
|
|
1640
1661
|
await fs6.writeFile(originalPath, buffer);
|
|
1641
1662
|
} else {
|
|
1642
1663
|
throw new Error(`File not found: ${imageKey}`);
|
|
1643
1664
|
}
|
|
1644
1665
|
}
|
|
1645
|
-
const ext =
|
|
1666
|
+
const ext = path6.extname(imageKey).toLowerCase();
|
|
1646
1667
|
const isSvg = ext === ".svg";
|
|
1647
1668
|
if (isSvg) {
|
|
1648
|
-
const imageDir =
|
|
1649
|
-
const imagesPath =
|
|
1669
|
+
const imageDir = path6.dirname(imageKey.slice(1));
|
|
1670
|
+
const imagesPath = getPublicPath("images", imageDir === "." ? "" : imageDir);
|
|
1650
1671
|
await fs6.mkdir(imagesPath, { recursive: true });
|
|
1651
|
-
const fileName =
|
|
1652
|
-
const destPath =
|
|
1672
|
+
const fileName = path6.basename(imageKey);
|
|
1673
|
+
const destPath = path6.join(imagesPath, fileName);
|
|
1653
1674
|
await fs6.writeFile(destPath, buffer);
|
|
1654
1675
|
meta[imageKey] = {
|
|
1655
1676
|
...entry,
|
|
@@ -1734,7 +1755,7 @@ async function handleProcessAllStream() {
|
|
|
1734
1755
|
let alreadyProcessed = 0;
|
|
1735
1756
|
const imagesToProcess = [];
|
|
1736
1757
|
for (const [key, entry] of getFileEntries(meta)) {
|
|
1737
|
-
const fileName =
|
|
1758
|
+
const fileName = path6.basename(key);
|
|
1738
1759
|
if (!isImageFile(fileName)) continue;
|
|
1739
1760
|
if (!isProcessed(entry)) {
|
|
1740
1761
|
imagesToProcess.push({ key, entry });
|
|
@@ -1746,7 +1767,7 @@ async function handleProcessAllStream() {
|
|
|
1746
1767
|
sendEvent({ type: "start", total });
|
|
1747
1768
|
for (let i = 0; i < imagesToProcess.length; i++) {
|
|
1748
1769
|
const { key, entry } = imagesToProcess[i];
|
|
1749
|
-
const fullPath =
|
|
1770
|
+
const fullPath = getPublicPath(key);
|
|
1750
1771
|
const existingCdnIndex = entry.c;
|
|
1751
1772
|
const existingCdnUrl = existingCdnIndex !== void 0 ? cdnUrls[existingCdnIndex] : void 0;
|
|
1752
1773
|
const isInOurR2 = existingCdnUrl === publicUrl;
|
|
@@ -1763,26 +1784,26 @@ async function handleProcessAllStream() {
|
|
|
1763
1784
|
let buffer;
|
|
1764
1785
|
if (isInOurR2) {
|
|
1765
1786
|
buffer = await downloadFromCdn(key);
|
|
1766
|
-
const dir =
|
|
1787
|
+
const dir = path6.dirname(fullPath);
|
|
1767
1788
|
await fs6.mkdir(dir, { recursive: true });
|
|
1768
1789
|
await fs6.writeFile(fullPath, buffer);
|
|
1769
1790
|
} else if (isRemote && existingCdnUrl) {
|
|
1770
1791
|
const remoteUrl = `${existingCdnUrl}${key}`;
|
|
1771
1792
|
buffer = await downloadFromRemoteUrl(remoteUrl);
|
|
1772
|
-
const dir =
|
|
1793
|
+
const dir = path6.dirname(fullPath);
|
|
1773
1794
|
await fs6.mkdir(dir, { recursive: true });
|
|
1774
1795
|
await fs6.writeFile(fullPath, buffer);
|
|
1775
1796
|
} else {
|
|
1776
1797
|
buffer = await fs6.readFile(fullPath);
|
|
1777
1798
|
}
|
|
1778
|
-
const ext =
|
|
1799
|
+
const ext = path6.extname(key).toLowerCase();
|
|
1779
1800
|
const isSvg = ext === ".svg";
|
|
1780
1801
|
if (isSvg) {
|
|
1781
|
-
const imageDir =
|
|
1782
|
-
const imagesPath =
|
|
1802
|
+
const imageDir = path6.dirname(key.slice(1));
|
|
1803
|
+
const imagesPath = getPublicPath("images", imageDir === "." ? "" : imageDir);
|
|
1783
1804
|
await fs6.mkdir(imagesPath, { recursive: true });
|
|
1784
|
-
const fileName =
|
|
1785
|
-
const destPath =
|
|
1805
|
+
const fileName = path6.basename(key);
|
|
1806
|
+
const destPath = path6.join(imagesPath, fileName);
|
|
1786
1807
|
await fs6.writeFile(destPath, buffer);
|
|
1787
1808
|
meta[key] = {
|
|
1788
1809
|
...entry,
|
|
@@ -1832,7 +1853,7 @@ async function handleProcessAllStream() {
|
|
|
1832
1853
|
const entries = await fs6.readdir(dir, { withFileTypes: true });
|
|
1833
1854
|
for (const fsEntry of entries) {
|
|
1834
1855
|
if (fsEntry.name.startsWith(".")) continue;
|
|
1835
|
-
const entryFullPath =
|
|
1856
|
+
const entryFullPath = path6.join(dir, fsEntry.name);
|
|
1836
1857
|
const relPath = relativePath ? `${relativePath}/${fsEntry.name}` : fsEntry.name;
|
|
1837
1858
|
if (fsEntry.isDirectory()) {
|
|
1838
1859
|
await findOrphans(entryFullPath, relPath);
|
|
@@ -1851,7 +1872,7 @@ async function handleProcessAllStream() {
|
|
|
1851
1872
|
} catch {
|
|
1852
1873
|
}
|
|
1853
1874
|
}
|
|
1854
|
-
const imagesDir =
|
|
1875
|
+
const imagesDir = getPublicPath("images");
|
|
1855
1876
|
try {
|
|
1856
1877
|
await findOrphans(imagesDir);
|
|
1857
1878
|
} catch {
|
|
@@ -1862,7 +1883,7 @@ async function handleProcessAllStream() {
|
|
|
1862
1883
|
let isEmpty = true;
|
|
1863
1884
|
for (const fsEntry of entries) {
|
|
1864
1885
|
if (fsEntry.isDirectory()) {
|
|
1865
|
-
const subDirEmpty = await removeEmptyDirs(
|
|
1886
|
+
const subDirEmpty = await removeEmptyDirs(path6.join(dir, fsEntry.name));
|
|
1866
1887
|
if (!subDirEmpty) isEmpty = false;
|
|
1867
1888
|
} else {
|
|
1868
1889
|
isEmpty = false;
|
|
@@ -1941,8 +1962,8 @@ async function handleDownloadStream(request) {
|
|
|
1941
1962
|
}
|
|
1942
1963
|
try {
|
|
1943
1964
|
const imageBuffer = await downloadFromCdn(imageKey);
|
|
1944
|
-
const localPath =
|
|
1945
|
-
await fs6.mkdir(
|
|
1965
|
+
const localPath = getPublicPath(imageKey.replace(/^\//, ""));
|
|
1966
|
+
await fs6.mkdir(path6.dirname(localPath), { recursive: true });
|
|
1946
1967
|
await fs6.writeFile(localPath, imageBuffer);
|
|
1947
1968
|
await deleteThumbnailsFromCdn(imageKey);
|
|
1948
1969
|
const wasProcessed = isProcessed(entry);
|
|
@@ -2007,7 +2028,7 @@ async function handleDownloadStream(request) {
|
|
|
2007
2028
|
// src/handlers/scan.ts
|
|
2008
2029
|
import { NextResponse as NextResponse4 } from "next/server";
|
|
2009
2030
|
import { promises as fs7 } from "fs";
|
|
2010
|
-
import
|
|
2031
|
+
import path7 from "path";
|
|
2011
2032
|
import sharp3 from "sharp";
|
|
2012
2033
|
import { encode as encode2 } from "blurhash";
|
|
2013
2034
|
async function handleScanStream() {
|
|
@@ -2033,7 +2054,7 @@ async function handleScanStream() {
|
|
|
2033
2054
|
const entries = await fs7.readdir(dir, { withFileTypes: true });
|
|
2034
2055
|
for (const entry of entries) {
|
|
2035
2056
|
if (entry.name.startsWith(".")) continue;
|
|
2036
|
-
const fullPath =
|
|
2057
|
+
const fullPath = path7.join(dir, entry.name);
|
|
2037
2058
|
const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
2038
2059
|
if (relPath === "images" || relPath.startsWith("images/")) continue;
|
|
2039
2060
|
if (entry.isDirectory()) {
|
|
@@ -2045,7 +2066,7 @@ async function handleScanStream() {
|
|
|
2045
2066
|
} catch {
|
|
2046
2067
|
}
|
|
2047
2068
|
}
|
|
2048
|
-
const publicDir =
|
|
2069
|
+
const publicDir = getPublicPath();
|
|
2049
2070
|
await scanDir(publicDir);
|
|
2050
2071
|
const total = allFiles.length;
|
|
2051
2072
|
sendEvent({ type: "start", total });
|
|
@@ -2063,7 +2084,7 @@ async function handleScanStream() {
|
|
|
2063
2084
|
continue;
|
|
2064
2085
|
}
|
|
2065
2086
|
if (meta[imageKey]) {
|
|
2066
|
-
const ext =
|
|
2087
|
+
const ext = path7.extname(relativePath);
|
|
2067
2088
|
const baseName = relativePath.slice(0, -ext.length);
|
|
2068
2089
|
let counter = 1;
|
|
2069
2090
|
let newKey = `/${baseName}-${counter}${ext}`;
|
|
@@ -2072,7 +2093,7 @@ async function handleScanStream() {
|
|
|
2072
2093
|
newKey = `/${baseName}-${counter}${ext}`;
|
|
2073
2094
|
}
|
|
2074
2095
|
const newRelativePath = `${baseName}-${counter}${ext}`;
|
|
2075
|
-
const newFullPath =
|
|
2096
|
+
const newFullPath = getPublicPath(newRelativePath);
|
|
2076
2097
|
try {
|
|
2077
2098
|
await fs7.rename(fullPath, newFullPath);
|
|
2078
2099
|
renamed.push({ from: relativePath, to: newRelativePath });
|
|
@@ -2088,7 +2109,7 @@ async function handleScanStream() {
|
|
|
2088
2109
|
try {
|
|
2089
2110
|
const isImage = isImageFile(relativePath);
|
|
2090
2111
|
if (isImage) {
|
|
2091
|
-
const ext =
|
|
2112
|
+
const ext = path7.extname(relativePath).toLowerCase();
|
|
2092
2113
|
if (ext === ".svg") {
|
|
2093
2114
|
meta[imageKey] = { o: { w: 0, h: 0 }, b: "" };
|
|
2094
2115
|
} else {
|
|
@@ -2130,7 +2151,7 @@ async function handleScanStream() {
|
|
|
2130
2151
|
const entries = await fs7.readdir(dir, { withFileTypes: true });
|
|
2131
2152
|
for (const entry of entries) {
|
|
2132
2153
|
if (entry.name.startsWith(".")) continue;
|
|
2133
|
-
const fullPath =
|
|
2154
|
+
const fullPath = path7.join(dir, entry.name);
|
|
2134
2155
|
const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
2135
2156
|
if (entry.isDirectory()) {
|
|
2136
2157
|
await findOrphans(fullPath, relPath);
|
|
@@ -2144,7 +2165,7 @@ async function handleScanStream() {
|
|
|
2144
2165
|
} catch {
|
|
2145
2166
|
}
|
|
2146
2167
|
}
|
|
2147
|
-
const imagesDir =
|
|
2168
|
+
const imagesDir = getPublicPath("images");
|
|
2148
2169
|
try {
|
|
2149
2170
|
await findOrphans(imagesDir);
|
|
2150
2171
|
} catch {
|
|
@@ -2188,7 +2209,7 @@ async function handleDeleteOrphans(request) {
|
|
|
2188
2209
|
errors.push(`Invalid path: ${orphanPath}`);
|
|
2189
2210
|
continue;
|
|
2190
2211
|
}
|
|
2191
|
-
const fullPath =
|
|
2212
|
+
const fullPath = getPublicPath(orphanPath);
|
|
2192
2213
|
try {
|
|
2193
2214
|
await fs7.unlink(fullPath);
|
|
2194
2215
|
deleted.push(orphanPath);
|
|
@@ -2197,14 +2218,14 @@ async function handleDeleteOrphans(request) {
|
|
|
2197
2218
|
errors.push(orphanPath);
|
|
2198
2219
|
}
|
|
2199
2220
|
}
|
|
2200
|
-
const imagesDir =
|
|
2221
|
+
const imagesDir = getPublicPath("images");
|
|
2201
2222
|
async function removeEmptyDirs(dir) {
|
|
2202
2223
|
try {
|
|
2203
2224
|
const entries = await fs7.readdir(dir, { withFileTypes: true });
|
|
2204
2225
|
let isEmpty = true;
|
|
2205
2226
|
for (const entry of entries) {
|
|
2206
2227
|
if (entry.isDirectory()) {
|
|
2207
|
-
const subDirEmpty = await removeEmptyDirs(
|
|
2228
|
+
const subDirEmpty = await removeEmptyDirs(path7.join(dir, entry.name));
|
|
2208
2229
|
if (!subDirEmpty) isEmpty = false;
|
|
2209
2230
|
} else {
|
|
2210
2231
|
isEmpty = false;
|
|
@@ -2239,8 +2260,8 @@ import { encode as encode3 } from "blurhash";
|
|
|
2239
2260
|
function parseImageUrl(url) {
|
|
2240
2261
|
const parsed = new URL(url);
|
|
2241
2262
|
const base = `${parsed.protocol}//${parsed.host}`;
|
|
2242
|
-
const
|
|
2243
|
-
return { base, path:
|
|
2263
|
+
const path9 = parsed.pathname;
|
|
2264
|
+
return { base, path: path9 };
|
|
2244
2265
|
}
|
|
2245
2266
|
async function processRemoteImage(url) {
|
|
2246
2267
|
const response = await fetch(url);
|
|
@@ -2289,20 +2310,20 @@ async function handleImportUrls(request) {
|
|
|
2289
2310
|
currentFile: url
|
|
2290
2311
|
});
|
|
2291
2312
|
try {
|
|
2292
|
-
const { base, path:
|
|
2293
|
-
const existingEntry = getMetaEntry(meta,
|
|
2313
|
+
const { base, path: path9 } = parseImageUrl(url);
|
|
2314
|
+
const existingEntry = getMetaEntry(meta, path9);
|
|
2294
2315
|
if (existingEntry) {
|
|
2295
|
-
skipped.push(
|
|
2316
|
+
skipped.push(path9);
|
|
2296
2317
|
continue;
|
|
2297
2318
|
}
|
|
2298
2319
|
const cdnIndex = getOrAddCdnIndex(meta, base);
|
|
2299
2320
|
const imageData = await processRemoteImage(url);
|
|
2300
|
-
setMetaEntry(meta,
|
|
2321
|
+
setMetaEntry(meta, path9, {
|
|
2301
2322
|
o: imageData.o,
|
|
2302
2323
|
b: imageData.b,
|
|
2303
2324
|
c: cdnIndex
|
|
2304
2325
|
});
|
|
2305
|
-
added.push(
|
|
2326
|
+
added.push(path9);
|
|
2306
2327
|
} catch (error) {
|
|
2307
2328
|
console.error(`Failed to import ${url}:`, error);
|
|
2308
2329
|
errors.push(url);
|
|
@@ -2360,7 +2381,7 @@ async function handleUpdateCdns(request) {
|
|
|
2360
2381
|
// src/handlers/favicon.ts
|
|
2361
2382
|
import { NextResponse as NextResponse5 } from "next/server";
|
|
2362
2383
|
import sharp5 from "sharp";
|
|
2363
|
-
import
|
|
2384
|
+
import path8 from "path";
|
|
2364
2385
|
import fs8 from "fs/promises";
|
|
2365
2386
|
var FAVICON_CONFIGS = [
|
|
2366
2387
|
{ name: "favicon.ico", size: 48 },
|
|
@@ -2379,13 +2400,13 @@ async function handleGenerateFavicon(request) {
|
|
|
2379
2400
|
} catch {
|
|
2380
2401
|
return NextResponse5.json({ error: "Invalid request body" }, { status: 400 });
|
|
2381
2402
|
}
|
|
2382
|
-
const fileName =
|
|
2403
|
+
const fileName = path8.basename(imagePath).toLowerCase();
|
|
2383
2404
|
if (fileName !== "favicon.png" && fileName !== "favicon.jpg") {
|
|
2384
2405
|
return NextResponse5.json({
|
|
2385
2406
|
error: "Source file must be named favicon.png or favicon.jpg"
|
|
2386
2407
|
}, { status: 400 });
|
|
2387
2408
|
}
|
|
2388
|
-
const sourcePath =
|
|
2409
|
+
const sourcePath = getPublicPath(imagePath.replace(/^\//, ""));
|
|
2389
2410
|
try {
|
|
2390
2411
|
await fs8.access(sourcePath);
|
|
2391
2412
|
} catch {
|
|
@@ -2397,7 +2418,7 @@ async function handleGenerateFavicon(request) {
|
|
|
2397
2418
|
} catch {
|
|
2398
2419
|
return NextResponse5.json({ error: "Source file is not a valid image" }, { status: 400 });
|
|
2399
2420
|
}
|
|
2400
|
-
const outputDir =
|
|
2421
|
+
const outputDir = getSrcAppPath();
|
|
2401
2422
|
try {
|
|
2402
2423
|
await fs8.access(outputDir);
|
|
2403
2424
|
} catch {
|
|
@@ -2431,7 +2452,7 @@ async function handleGenerateFavicon(request) {
|
|
|
2431
2452
|
message: `Generating ${config.name} (${config.size}x${config.size})...`
|
|
2432
2453
|
});
|
|
2433
2454
|
try {
|
|
2434
|
-
const outputPath =
|
|
2455
|
+
const outputPath = path8.join(outputDir, config.name);
|
|
2435
2456
|
await sharp5(sourcePath).resize(config.size, config.size, {
|
|
2436
2457
|
fit: "cover",
|
|
2437
2458
|
position: "center"
|