@gallop.software/studio 2.3.25 → 2.3.27
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/server/index.js +58 -11
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -95,6 +95,17 @@ function getFileEntries(meta) {
|
|
|
95
95
|
|
|
96
96
|
// src/handlers/utils/files.ts
|
|
97
97
|
import path2 from "path";
|
|
98
|
+
function slugifyFilename(filename) {
|
|
99
|
+
const ext = path2.extname(filename).toLowerCase();
|
|
100
|
+
const baseName = path2.basename(filename, path2.extname(filename));
|
|
101
|
+
const slugged = baseName.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[_\s]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
102
|
+
const finalSlug = slugged || "file";
|
|
103
|
+
return finalSlug + ext;
|
|
104
|
+
}
|
|
105
|
+
function slugifyFolderName(name) {
|
|
106
|
+
const slugged = name.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[_\s]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
107
|
+
return slugged || "folder";
|
|
108
|
+
}
|
|
98
109
|
function isImageFile(filename) {
|
|
99
110
|
const ext = path2.extname(filename).toLowerCase();
|
|
100
111
|
return [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".ico", ".bmp", ".tiff", ".tif"].includes(ext);
|
|
@@ -375,6 +386,20 @@ async function deleteThumbnailsFromCdn(imageKey) {
|
|
|
375
386
|
}
|
|
376
387
|
}
|
|
377
388
|
}
|
|
389
|
+
async function deleteOriginalFromCdn(imageKey) {
|
|
390
|
+
const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;
|
|
391
|
+
if (!bucketName) throw new Error("R2 bucket not configured");
|
|
392
|
+
const r2 = getR2Client();
|
|
393
|
+
try {
|
|
394
|
+
await r2.send(
|
|
395
|
+
new DeleteObjectCommand({
|
|
396
|
+
Bucket: bucketName,
|
|
397
|
+
Key: imageKey.replace(/^\//, "")
|
|
398
|
+
})
|
|
399
|
+
);
|
|
400
|
+
} catch {
|
|
401
|
+
}
|
|
402
|
+
}
|
|
378
403
|
|
|
379
404
|
// src/handlers/utils/response.ts
|
|
380
405
|
function jsonResponse(data, init) {
|
|
@@ -1033,7 +1058,7 @@ async function handleUpload(request) {
|
|
|
1033
1058
|
}
|
|
1034
1059
|
const bytes = await file.arrayBuffer();
|
|
1035
1060
|
const buffer = Buffer.from(bytes);
|
|
1036
|
-
const fileName = file.name;
|
|
1061
|
+
const fileName = slugifyFilename(file.name);
|
|
1037
1062
|
const ext = path6.extname(fileName).toLowerCase();
|
|
1038
1063
|
const isImage = isImageFile(fileName);
|
|
1039
1064
|
const isMedia = isMediaFile(fileName);
|
|
@@ -1225,7 +1250,7 @@ async function handleCreateFolder(request) {
|
|
|
1225
1250
|
if (!name || typeof name !== "string") {
|
|
1226
1251
|
return jsonResponse({ error: "Folder name is required" }, { status: 400 });
|
|
1227
1252
|
}
|
|
1228
|
-
const sanitizedName = name
|
|
1253
|
+
const sanitizedName = slugifyFolderName(name);
|
|
1229
1254
|
if (!sanitizedName) {
|
|
1230
1255
|
return jsonResponse({ error: "Invalid folder name" }, { status: 400 });
|
|
1231
1256
|
}
|
|
@@ -1252,14 +1277,8 @@ async function handleRename(request) {
|
|
|
1252
1277
|
if (!oldPath || !newName) {
|
|
1253
1278
|
return jsonResponse({ error: "Path and new name are required" }, { status: 400 });
|
|
1254
1279
|
}
|
|
1255
|
-
const sanitizedName = newName.replace(/[<>:"/\\|?*]/g, "").trim();
|
|
1256
|
-
if (!sanitizedName) {
|
|
1257
|
-
return jsonResponse({ error: "Invalid name" }, { status: 400 });
|
|
1258
|
-
}
|
|
1259
1280
|
const safePath = oldPath.replace(/\.\./g, "");
|
|
1260
1281
|
const absoluteOldPath = getWorkspacePath(safePath);
|
|
1261
|
-
const parentDir = path6.dirname(absoluteOldPath);
|
|
1262
|
-
const absoluteNewPath = path6.join(parentDir, sanitizedName);
|
|
1263
1282
|
if (!absoluteOldPath.startsWith(getPublicPath())) {
|
|
1264
1283
|
return jsonResponse({ error: "Invalid path" }, { status: 400 });
|
|
1265
1284
|
}
|
|
@@ -1268,14 +1287,20 @@ async function handleRename(request) {
|
|
|
1268
1287
|
} catch {
|
|
1269
1288
|
return jsonResponse({ error: "File or folder not found" }, { status: 404 });
|
|
1270
1289
|
}
|
|
1290
|
+
const stats = await fs6.stat(absoluteOldPath);
|
|
1291
|
+
const isFile = stats.isFile();
|
|
1292
|
+
const isImage = isFile && isImageFile(path6.basename(oldPath));
|
|
1293
|
+
const sanitizedName = isFile ? slugifyFilename(newName) : slugifyFolderName(newName);
|
|
1294
|
+
if (!sanitizedName) {
|
|
1295
|
+
return jsonResponse({ error: "Invalid name" }, { status: 400 });
|
|
1296
|
+
}
|
|
1297
|
+
const parentDir = path6.dirname(absoluteOldPath);
|
|
1298
|
+
const absoluteNewPath = path6.join(parentDir, sanitizedName);
|
|
1271
1299
|
try {
|
|
1272
1300
|
await fs6.access(absoluteNewPath);
|
|
1273
1301
|
return jsonResponse({ error: "An item with this name already exists" }, { status: 400 });
|
|
1274
1302
|
} catch {
|
|
1275
1303
|
}
|
|
1276
|
-
const stats = await fs6.stat(absoluteOldPath);
|
|
1277
|
-
const isFile = stats.isFile();
|
|
1278
|
-
const isImage = isFile && isImageFile(path6.basename(oldPath));
|
|
1279
1304
|
await fs6.rename(absoluteOldPath, absoluteNewPath);
|
|
1280
1305
|
if (isImage) {
|
|
1281
1306
|
const meta = await loadMeta();
|
|
@@ -1903,7 +1928,9 @@ async function handleReprocessStream(request) {
|
|
|
1903
1928
|
const updatedEntry = await processImage(buffer, imageKey);
|
|
1904
1929
|
if (isInOurR2) {
|
|
1905
1930
|
updatedEntry.c = existingCdnIndex;
|
|
1931
|
+
await deleteOriginalFromCdn(imageKey);
|
|
1906
1932
|
await deleteThumbnailsFromCdn(imageKey);
|
|
1933
|
+
await uploadOriginalToCdn(imageKey);
|
|
1907
1934
|
await uploadToCdn(imageKey);
|
|
1908
1935
|
await deleteLocalThumbnails(imageKey);
|
|
1909
1936
|
try {
|
|
@@ -2363,6 +2390,26 @@ async function handleScanStream() {
|
|
|
2363
2390
|
}
|
|
2364
2391
|
continue;
|
|
2365
2392
|
}
|
|
2393
|
+
const dirName = path8.dirname(relativePath);
|
|
2394
|
+
const originalFileName = path8.basename(relativePath);
|
|
2395
|
+
const sluggedFileName = slugifyFilename(originalFileName);
|
|
2396
|
+
if (sluggedFileName !== originalFileName) {
|
|
2397
|
+
const newRelativePath = dirName === "." ? sluggedFileName : `${dirName}/${sluggedFileName}`;
|
|
2398
|
+
const newFullPath = getPublicPath(newRelativePath);
|
|
2399
|
+
const newKey = "/" + newRelativePath;
|
|
2400
|
+
if (!meta[newKey] && !existingKeys.has(newKey)) {
|
|
2401
|
+
try {
|
|
2402
|
+
await fs8.mkdir(path8.dirname(newFullPath), { recursive: true });
|
|
2403
|
+
await fs8.rename(fullPath, newFullPath);
|
|
2404
|
+
renamed.push({ from: relativePath, to: newRelativePath });
|
|
2405
|
+
relativePath = newRelativePath;
|
|
2406
|
+
fullPath = newFullPath;
|
|
2407
|
+
imageKey = newKey;
|
|
2408
|
+
} catch (err) {
|
|
2409
|
+
console.error(`Failed to slugify ${relativePath}:`, err);
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2366
2413
|
if (meta[imageKey]) {
|
|
2367
2414
|
const ext = path8.extname(relativePath);
|
|
2368
2415
|
const baseName = relativePath.slice(0, -ext.length);
|