@gallop.software/studio 0.1.115 → 1.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/dist/{StudioUI-OVL65ONP.js → StudioUI-GWMM47P7.js} +99 -17
- package/dist/StudioUI-GWMM47P7.js.map +1 -0
- package/dist/{StudioUI-73XFVFV4.mjs → StudioUI-KCUI5YUD.mjs} +99 -17
- package/dist/StudioUI-KCUI5YUD.mjs.map +1 -0
- package/dist/{chunk-RDNC5ABF.mjs → chunk-FDWPNRNZ.mjs} +1 -1
- package/dist/chunk-FDWPNRNZ.mjs.map +1 -0
- package/dist/{chunk-LEOQKJCL.js → chunk-WJJHVPLT.js} +1 -1
- package/dist/chunk-WJJHVPLT.js.map +1 -0
- package/dist/handlers/index.js +126 -27
- package/dist/handlers/index.js.map +1 -1
- package/dist/handlers/index.mjs +116 -17
- package/dist/handlers/index.mjs.map +1 -1
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -3
- package/dist/index.mjs +2 -2
- package/package.json +1 -1
- package/dist/StudioUI-73XFVFV4.mjs.map +0 -1
- package/dist/StudioUI-OVL65ONP.js.map +0 -1
- package/dist/chunk-LEOQKJCL.js.map +0 -1
- package/dist/chunk-RDNC5ABF.mjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["/**\n * Meta entry - works for images and non-images\n * Images have w, h, b (after processing)\n * c is the index into _cdns array (omit if not on CDN)\n */\nexport interface MetaEntry {\n w?: number // original width (images only)\n h?: number // original height (images only)\n b?: string // blurhash (images only, after processing)\n p?: 1 // processed (has thumbnails and blurhash)\n c?: number // CDN index - index into _cdns array (omit if not on CDN)\n}\n\n/**\n * Full meta schema including special keys\n * _cdns: Array of CDN base URLs\n * Other keys: file paths from public folder\n */\nexport interface FullMeta {\n _cdns?: string[] // Array of CDN base URLs\n [key: string]: MetaEntry | string[] | undefined\n}\n\n/**\n * Meta schema - keyed by path from public folder (legacy type)\n * Example: { \"/portfolio/photo.jpg\": { w: 2400, h: 1600, b: \"...\" } }\n */\nexport type LeanMeta = Record<string, MetaEntry>\n\n// Legacy alias for compatibility\nexport type LeanImageEntry = MetaEntry\n\n/**\n * File/folder item for browser\n */\nexport interface FileItem {\n name: string\n path: string\n type: 'file' | 'folder'\n size?: number\n dimensions?: { width: number; height: number }\n isProcessed?: boolean\n cdnPushed?: boolean\n cdnBaseUrl?: string // CDN base URL when pushed to cloud\n isRemote?: boolean // true if CDN URL doesn't match R2 (external import)\n isProtected?: boolean // true for images folder and its contents (cannot select/modify)\n // Folder-specific properties\n fileCount?: number\n totalSize?: number\n // For showing thumbnails - path to -sm version if exists\n thumbnail?: string\n // Whether a processed thumbnail exists\n hasThumbnail?: boolean\n}\n\n/**\n * Studio configuration\n */\nexport interface StudioConfig {\n r2AccountId?: string\n r2AccessKeyId?: string\n r2SecretAccessKey?: string\n r2BucketName?: string\n r2PublicUrl?: string\n thumbnailSizes?: {\n small: number\n medium: number\n large: number\n }\n}\n\n/**\n * Get thumbnail path from original image path\n */\nexport function getThumbnailPath(originalPath: string, size: 'sm' | 'md' | 'lg' | 'full'): string {\n if (size === 'full') {\n const ext = originalPath.match(/\\.\\w+$/)?.[0] || '.jpg'\n const base = originalPath.replace(/\\.\\w+$/, '')\n const outputExt = ext.toLowerCase() === '.png' ? '.png' : '.jpg'\n return `/images${base}${outputExt}`\n }\n const ext = originalPath.match(/\\.\\w+$/)?.[0] || '.jpg'\n const base = originalPath.replace(/\\.\\w+$/, '')\n const outputExt = ext.toLowerCase() === '.png' ? '.png' : '.jpg'\n return `/images${base}-${size}${outputExt}`\n}\n\n/**\n * Get all thumbnail paths for an image\n */\nexport function getAllThumbnailPaths(originalPath: string): string[] {\n return [\n getThumbnailPath(originalPath, 'full'),\n getThumbnailPath(originalPath, 'lg'),\n getThumbnailPath(originalPath, 'md'),\n getThumbnailPath(originalPath, 'sm'),\n ]\n}\n"],"mappings":";AA0EO,SAAS,iBAAiB,cAAsB,MAA2C;AAChG,MAAI,SAAS,QAAQ;AACnB,UAAMA,OAAM,aAAa,MAAM,QAAQ,IAAI,CAAC,KAAK;AACjD,UAAMC,QAAO,aAAa,QAAQ,UAAU,EAAE;AAC9C,UAAMC,aAAYF,KAAI,YAAY,MAAM,SAAS,SAAS;AAC1D,WAAO,UAAUC,KAAI,GAAGC,UAAS;AAAA,EACnC;AACA,QAAM,MAAM,aAAa,MAAM,QAAQ,IAAI,CAAC,KAAK;AACjD,QAAM,OAAO,aAAa,QAAQ,UAAU,EAAE;AAC9C,QAAM,YAAY,IAAI,YAAY,MAAM,SAAS,SAAS;AAC1D,SAAO,UAAU,IAAI,IAAI,IAAI,GAAG,SAAS;AAC3C;AAKO,SAAS,qBAAqB,cAAgC;AACnE,SAAO;AAAA,IACL,iBAAiB,cAAc,MAAM;AAAA,IACrC,iBAAiB,cAAc,IAAI;AAAA,IACnC,iBAAiB,cAAc,IAAI;AAAA,IACnC,iBAAiB,cAAc,IAAI;AAAA,EACrC;AACF;","names":["ext","base","outputExt"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/chrisb/Sites/studio/dist/chunk-WJJHVPLT.js","../src/types.ts"],"names":["ext","base","outputExt"],"mappings":"AAAA;AC0EO,SAAS,gBAAA,CAAiB,YAAA,EAAsB,IAAA,EAA2C;AAChG,EAAA,GAAA,CAAI,KAAA,IAAS,MAAA,EAAQ;AACnB,IAAA,MAAMA,KAAAA,kBAAM,YAAA,mBAAa,KAAA,mBAAM,QAAQ,CAAA,4BAAA,CAAI,CAAC,IAAA,GAAK,MAAA;AACjD,IAAA,MAAMC,MAAAA,EAAO,YAAA,CAAa,OAAA,CAAQ,QAAA,EAAU,EAAE,CAAA;AAC9C,IAAA,MAAMC,WAAAA,EAAYF,IAAAA,CAAI,WAAA,CAAY,EAAA,IAAM,OAAA,EAAS,OAAA,EAAS,MAAA;AAC1D,IAAA,OAAO,CAAA,OAAA,EAAUC,KAAI,CAAA,EAAA;AACvB,EAAA;AACyB,EAAA;AACZ,EAAA;AACS,EAAA;AACG,EAAA;AAC3B;AAKgB;AACP,EAAA;AACY,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACnB,EAAA;AACF;AD5E2B;AACA;AACA;AACA;AACA","file":"/Users/chrisb/Sites/studio/dist/chunk-WJJHVPLT.js","sourcesContent":[null,"/**\n * Meta entry - works for images and non-images\n * Images have w, h, b (after processing)\n * c is the index into _cdns array (omit if not on CDN)\n */\nexport interface MetaEntry {\n w?: number // original width (images only)\n h?: number // original height (images only)\n b?: string // blurhash (images only, after processing)\n p?: 1 // processed (has thumbnails and blurhash)\n c?: number // CDN index - index into _cdns array (omit if not on CDN)\n}\n\n/**\n * Full meta schema including special keys\n * _cdns: Array of CDN base URLs\n * Other keys: file paths from public folder\n */\nexport interface FullMeta {\n _cdns?: string[] // Array of CDN base URLs\n [key: string]: MetaEntry | string[] | undefined\n}\n\n/**\n * Meta schema - keyed by path from public folder (legacy type)\n * Example: { \"/portfolio/photo.jpg\": { w: 2400, h: 1600, b: \"...\" } }\n */\nexport type LeanMeta = Record<string, MetaEntry>\n\n// Legacy alias for compatibility\nexport type LeanImageEntry = MetaEntry\n\n/**\n * File/folder item for browser\n */\nexport interface FileItem {\n name: string\n path: string\n type: 'file' | 'folder'\n size?: number\n dimensions?: { width: number; height: number }\n isProcessed?: boolean\n cdnPushed?: boolean\n cdnBaseUrl?: string // CDN base URL when pushed to cloud\n isRemote?: boolean // true if CDN URL doesn't match R2 (external import)\n isProtected?: boolean // true for images folder and its contents (cannot select/modify)\n // Folder-specific properties\n fileCount?: number\n totalSize?: number\n // For showing thumbnails - path to -sm version if exists\n thumbnail?: string\n // Whether a processed thumbnail exists\n hasThumbnail?: boolean\n}\n\n/**\n * Studio configuration\n */\nexport interface StudioConfig {\n r2AccountId?: string\n r2AccessKeyId?: string\n r2SecretAccessKey?: string\n r2BucketName?: string\n r2PublicUrl?: string\n thumbnailSizes?: {\n small: number\n medium: number\n large: number\n }\n}\n\n/**\n * Get thumbnail path from original image path\n */\nexport function getThumbnailPath(originalPath: string, size: 'sm' | 'md' | 'lg' | 'full'): string {\n if (size === 'full') {\n const ext = originalPath.match(/\\.\\w+$/)?.[0] || '.jpg'\n const base = originalPath.replace(/\\.\\w+$/, '')\n const outputExt = ext.toLowerCase() === '.png' ? '.png' : '.jpg'\n return `/images${base}${outputExt}`\n }\n const ext = originalPath.match(/\\.\\w+$/)?.[0] || '.jpg'\n const base = originalPath.replace(/\\.\\w+$/, '')\n const outputExt = ext.toLowerCase() === '.png' ? '.png' : '.jpg'\n return `/images${base}-${size}${outputExt}`\n}\n\n/**\n * Get all thumbnail paths for an image\n */\nexport function getAllThumbnailPaths(originalPath: string): string[] {\n return [\n getThumbnailPath(originalPath, 'full'),\n getThumbnailPath(originalPath, 'lg'),\n getThumbnailPath(originalPath, 'md'),\n getThumbnailPath(originalPath, 'sm'),\n ]\n}\n"]}
|
package/dist/handlers/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
var
|
|
4
|
+
var _chunkWJJHVPLTjs = require('../chunk-WJJHVPLT.js');
|
|
5
5
|
|
|
6
6
|
// src/handlers/index.ts
|
|
7
7
|
var _server = require('next/server');
|
|
@@ -219,7 +219,7 @@ async function uploadToCdn(imageKey) {
|
|
|
219
219
|
const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;
|
|
220
220
|
if (!bucketName) throw new Error("R2 bucket not configured");
|
|
221
221
|
const r2 = getR2Client();
|
|
222
|
-
for (const thumbPath of
|
|
222
|
+
for (const thumbPath of _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
223
223
|
const localPath = _path2.default.join(process.cwd(), "public", thumbPath);
|
|
224
224
|
try {
|
|
225
225
|
const fileBuffer = await _fs.promises.readFile(localPath);
|
|
@@ -236,7 +236,7 @@ async function uploadToCdn(imageKey) {
|
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
238
|
async function deleteLocalThumbnails(imageKey) {
|
|
239
|
-
for (const thumbPath of
|
|
239
|
+
for (const thumbPath of _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
240
240
|
const localPath = _path2.default.join(process.cwd(), "public", thumbPath);
|
|
241
241
|
try {
|
|
242
242
|
await _fs.promises.unlink(localPath);
|
|
@@ -281,7 +281,7 @@ async function deleteFromCdn(imageKey, hasThumbnails) {
|
|
|
281
281
|
} catch (e4) {
|
|
282
282
|
}
|
|
283
283
|
if (hasThumbnails) {
|
|
284
|
-
for (const thumbPath of
|
|
284
|
+
for (const thumbPath of _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
285
285
|
try {
|
|
286
286
|
await r2.send(
|
|
287
287
|
new (0, _clients3.DeleteObjectCommand)({
|
|
@@ -309,13 +309,16 @@ async function handleList(request) {
|
|
|
309
309
|
const items = [];
|
|
310
310
|
const seenFolders = /* @__PURE__ */ new Set();
|
|
311
311
|
const metaKeys = fileEntries.map(([key]) => key);
|
|
312
|
+
const isInsideImagesFolder = relativePath === "images" || relativePath.startsWith("images/");
|
|
312
313
|
const absoluteDir = _path2.default.join(process.cwd(), requestedPath);
|
|
313
314
|
try {
|
|
314
315
|
const dirEntries = await _fs.promises.readdir(absoluteDir, { withFileTypes: true });
|
|
315
316
|
for (const entry of dirEntries) {
|
|
316
|
-
if (entry.isDirectory() && !entry.name.startsWith(".")
|
|
317
|
+
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
317
318
|
if (!seenFolders.has(entry.name)) {
|
|
318
319
|
seenFolders.add(entry.name);
|
|
320
|
+
const isImagesFolder = entry.name === "images" && !relativePath;
|
|
321
|
+
const folderPath = relativePath ? `public/${relativePath}/${entry.name}` : `public/${entry.name}`;
|
|
319
322
|
const folderPrefix = pathPrefix === "/" ? `/${entry.name}/` : `${pathPrefix}${entry.name}/`;
|
|
320
323
|
let fileCount = 0;
|
|
321
324
|
for (const k of metaKeys) {
|
|
@@ -323,9 +326,10 @@ async function handleList(request) {
|
|
|
323
326
|
}
|
|
324
327
|
items.push({
|
|
325
328
|
name: entry.name,
|
|
326
|
-
path:
|
|
329
|
+
path: folderPath,
|
|
327
330
|
type: "folder",
|
|
328
|
-
fileCount
|
|
331
|
+
fileCount,
|
|
332
|
+
isProtected: isImagesFolder || isInsideImagesFolder
|
|
329
333
|
});
|
|
330
334
|
}
|
|
331
335
|
}
|
|
@@ -354,7 +358,8 @@ async function handleList(request) {
|
|
|
354
358
|
name: folderName,
|
|
355
359
|
path: relativePath ? `public/${relativePath}/${folderName}` : `public/${folderName}`,
|
|
356
360
|
type: "folder",
|
|
357
|
-
fileCount
|
|
361
|
+
fileCount,
|
|
362
|
+
isProtected: isInsideImagesFolder
|
|
358
363
|
});
|
|
359
364
|
}
|
|
360
365
|
} else {
|
|
@@ -367,7 +372,7 @@ async function handleList(request) {
|
|
|
367
372
|
let hasThumbnail = false;
|
|
368
373
|
let fileSize;
|
|
369
374
|
if (isImage && entry.p === 1) {
|
|
370
|
-
const thumbPath =
|
|
375
|
+
const thumbPath = _chunkWJJHVPLTjs.getThumbnailPath.call(void 0, key, "sm");
|
|
371
376
|
if (isPushedToCloud && entry.c !== void 0) {
|
|
372
377
|
const cdnUrl = cdnUrls[entry.c];
|
|
373
378
|
if (cdnUrl) {
|
|
@@ -413,6 +418,7 @@ async function handleList(request) {
|
|
|
413
418
|
cdnPushed: isPushedToCloud,
|
|
414
419
|
cdnBaseUrl: fileCdnUrl,
|
|
415
420
|
isRemote,
|
|
421
|
+
isProtected: isInsideImagesFolder,
|
|
416
422
|
dimensions: entry.w && entry.h ? { width: entry.w, height: entry.h } : void 0
|
|
417
423
|
});
|
|
418
424
|
}
|
|
@@ -446,7 +452,7 @@ async function handleSearch(request) {
|
|
|
446
452
|
let thumbnail;
|
|
447
453
|
let hasThumbnail = false;
|
|
448
454
|
if (isImage && entry.p === 1) {
|
|
449
|
-
const thumbPath =
|
|
455
|
+
const thumbPath = _chunkWJJHVPLTjs.getThumbnailPath.call(void 0, key, "sm");
|
|
450
456
|
if (isPushedToCloud && entry.c !== void 0) {
|
|
451
457
|
const cdnUrl = cdnUrls[entry.c];
|
|
452
458
|
if (cdnUrl) {
|
|
@@ -701,7 +707,7 @@ async function handleDelete(request) {
|
|
|
701
707
|
for (const key of Object.keys(meta)) {
|
|
702
708
|
if (key.startsWith(prefix) || key === imageKey) {
|
|
703
709
|
if (!meta[key].c) {
|
|
704
|
-
for (const thumbPath of
|
|
710
|
+
for (const thumbPath of _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, key)) {
|
|
705
711
|
const absoluteThumbPath = _path2.default.join(process.cwd(), "public", thumbPath);
|
|
706
712
|
try {
|
|
707
713
|
await _fs.promises.unlink(absoluteThumbPath);
|
|
@@ -717,7 +723,7 @@ async function handleDelete(request) {
|
|
|
717
723
|
const isInImagesFolder = itemPath.startsWith("public/images/");
|
|
718
724
|
if (!isInImagesFolder && entry) {
|
|
719
725
|
if (!isPushedToCloud) {
|
|
720
|
-
for (const thumbPath of
|
|
726
|
+
for (const thumbPath of _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
721
727
|
const absoluteThumbPath = _path2.default.join(process.cwd(), "public", thumbPath);
|
|
722
728
|
try {
|
|
723
729
|
await _fs.promises.unlink(absoluteThumbPath);
|
|
@@ -829,8 +835,8 @@ async function handleRename(request) {
|
|
|
829
835
|
const newKey = "/" + newRelativePath;
|
|
830
836
|
if (meta[oldKey]) {
|
|
831
837
|
const entry = meta[oldKey];
|
|
832
|
-
const oldThumbPaths =
|
|
833
|
-
const newThumbPaths =
|
|
838
|
+
const oldThumbPaths = _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, oldKey);
|
|
839
|
+
const newThumbPaths = _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, newKey);
|
|
834
840
|
for (let i = 0; i < oldThumbPaths.length; i++) {
|
|
835
841
|
const oldThumbPath = _path2.default.join(process.cwd(), "public", oldThumbPaths[i]);
|
|
836
842
|
const newThumbPath = _path2.default.join(process.cwd(), "public", newThumbPaths[i]);
|
|
@@ -982,8 +988,8 @@ async function handleMoveStream(request) {
|
|
|
982
988
|
await _fs.promises.rename(absolutePath, newAbsolutePath);
|
|
983
989
|
const stats = await _fs.promises.stat(newAbsolutePath);
|
|
984
990
|
if (stats.isFile() && isImage && entry) {
|
|
985
|
-
const oldThumbPaths =
|
|
986
|
-
const newThumbPaths =
|
|
991
|
+
const oldThumbPaths = _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, oldKey);
|
|
992
|
+
const newThumbPaths = _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, newKey);
|
|
987
993
|
for (let j = 0; j < oldThumbPaths.length; j++) {
|
|
988
994
|
const oldThumbPath = _path2.default.join(process.cwd(), "public", oldThumbPaths[j]);
|
|
989
995
|
const newThumbPath = _path2.default.join(process.cwd(), "public", newThumbPaths[j]);
|
|
@@ -1083,10 +1089,6 @@ async function handleSync(request) {
|
|
|
1083
1089
|
continue;
|
|
1084
1090
|
}
|
|
1085
1091
|
const isRemote = entry.c !== void 0 && existingCdnUrl !== publicUrl;
|
|
1086
|
-
if (!isRemote && !entry.p) {
|
|
1087
|
-
errors.push(`Image not processed: ${imageKey}. Run Process Images first.`);
|
|
1088
|
-
continue;
|
|
1089
|
-
}
|
|
1090
1092
|
try {
|
|
1091
1093
|
let originalBuffer;
|
|
1092
1094
|
if (isRemote) {
|
|
@@ -1111,7 +1113,7 @@ async function handleSync(request) {
|
|
|
1111
1113
|
);
|
|
1112
1114
|
urlsToPurge.push(`${publicUrl}${imageKey}`);
|
|
1113
1115
|
if (!isRemote && entry.p) {
|
|
1114
|
-
for (const thumbPath of
|
|
1116
|
+
for (const thumbPath of _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
1115
1117
|
const localPath = _path2.default.join(process.cwd(), "public", thumbPath);
|
|
1116
1118
|
try {
|
|
1117
1119
|
const fileBuffer = await _fs.promises.readFile(localPath);
|
|
@@ -1131,7 +1133,7 @@ async function handleSync(request) {
|
|
|
1131
1133
|
entry.c = cdnIndex;
|
|
1132
1134
|
if (!isRemote) {
|
|
1133
1135
|
const originalLocalPath = _path2.default.join(process.cwd(), "public", imageKey);
|
|
1134
|
-
for (const thumbPath of
|
|
1136
|
+
for (const thumbPath of _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
1135
1137
|
const localPath = _path2.default.join(process.cwd(), "public", thumbPath);
|
|
1136
1138
|
try {
|
|
1137
1139
|
await _fs.promises.unlink(localPath);
|
|
@@ -1206,7 +1208,7 @@ async function handleReprocess(request) {
|
|
|
1206
1208
|
if (isInOurR2) {
|
|
1207
1209
|
updatedEntry.c = existingCdnIndex;
|
|
1208
1210
|
await uploadToCdn(imageKey);
|
|
1209
|
-
for (const thumbPath of
|
|
1211
|
+
for (const thumbPath of _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
1210
1212
|
urlsToPurge.push(`${publicUrl}${thumbPath}`);
|
|
1211
1213
|
}
|
|
1212
1214
|
await deleteLocalThumbnails(imageKey);
|
|
@@ -1327,7 +1329,7 @@ async function handleProcessAllStream() {
|
|
|
1327
1329
|
}
|
|
1328
1330
|
if (isInOurR2) {
|
|
1329
1331
|
await uploadToCdn(key);
|
|
1330
|
-
for (const thumbPath of
|
|
1332
|
+
for (const thumbPath of _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, key)) {
|
|
1331
1333
|
urlsToPurge.push(`${publicUrl}${thumbPath}`);
|
|
1332
1334
|
}
|
|
1333
1335
|
await deleteLocalThumbnails(key);
|
|
@@ -1346,7 +1348,7 @@ async function handleProcessAllStream() {
|
|
|
1346
1348
|
const trackedPaths = /* @__PURE__ */ new Set();
|
|
1347
1349
|
for (const [imageKey, entry] of getFileEntries(meta)) {
|
|
1348
1350
|
if (entry.c === void 0) {
|
|
1349
|
-
for (const thumbPath of
|
|
1351
|
+
for (const thumbPath of _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
1350
1352
|
trackedPaths.add(thumbPath);
|
|
1351
1353
|
}
|
|
1352
1354
|
}
|
|
@@ -1437,6 +1439,7 @@ async function handleProcessAllStream() {
|
|
|
1437
1439
|
|
|
1438
1440
|
|
|
1439
1441
|
|
|
1442
|
+
|
|
1440
1443
|
async function handleScanStream() {
|
|
1441
1444
|
const encoder = new TextEncoder();
|
|
1442
1445
|
const stream = new ReadableStream({
|
|
@@ -1448,11 +1451,12 @@ async function handleScanStream() {
|
|
|
1448
1451
|
};
|
|
1449
1452
|
try {
|
|
1450
1453
|
const meta = await loadMeta();
|
|
1451
|
-
const existingCount = Object.keys(meta).length;
|
|
1454
|
+
const existingCount = Object.keys(meta).filter((k) => !k.startsWith("_")).length;
|
|
1452
1455
|
const existingKeys = new Set(Object.keys(meta));
|
|
1453
1456
|
const added = [];
|
|
1454
1457
|
const renamed = [];
|
|
1455
1458
|
const errors = [];
|
|
1459
|
+
const orphanedFiles = [];
|
|
1456
1460
|
const allFiles = [];
|
|
1457
1461
|
async function scanDir(dir, relativePath = "") {
|
|
1458
1462
|
try {
|
|
@@ -1542,6 +1546,40 @@ async function handleScanStream() {
|
|
|
1542
1546
|
errors.push(relativePath);
|
|
1543
1547
|
}
|
|
1544
1548
|
}
|
|
1549
|
+
sendEvent({ type: "cleanup", message: "Checking for orphaned thumbnails..." });
|
|
1550
|
+
const expectedThumbnails = /* @__PURE__ */ new Set();
|
|
1551
|
+
const fileEntries = getFileEntries(meta);
|
|
1552
|
+
for (const [imageKey, entry] of fileEntries) {
|
|
1553
|
+
if (entry.c === void 0 && entry.p === 1) {
|
|
1554
|
+
for (const thumbPath of _chunkWJJHVPLTjs.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
1555
|
+
expectedThumbnails.add(thumbPath);
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
async function findOrphans(dir, relativePath = "") {
|
|
1560
|
+
try {
|
|
1561
|
+
const entries = await _fs.promises.readdir(dir, { withFileTypes: true });
|
|
1562
|
+
for (const entry of entries) {
|
|
1563
|
+
if (entry.name.startsWith(".")) continue;
|
|
1564
|
+
const fullPath = _path2.default.join(dir, entry.name);
|
|
1565
|
+
const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
1566
|
+
if (entry.isDirectory()) {
|
|
1567
|
+
await findOrphans(fullPath, relPath);
|
|
1568
|
+
} else if (isImageFile(entry.name)) {
|
|
1569
|
+
const publicPath = `/images/${relPath}`;
|
|
1570
|
+
if (!expectedThumbnails.has(publicPath)) {
|
|
1571
|
+
orphanedFiles.push(publicPath);
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
} catch (e36) {
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
const imagesDir = _path2.default.join(process.cwd(), "public", "images");
|
|
1579
|
+
try {
|
|
1580
|
+
await findOrphans(imagesDir);
|
|
1581
|
+
} catch (e37) {
|
|
1582
|
+
}
|
|
1545
1583
|
await saveMeta(meta);
|
|
1546
1584
|
sendEvent({
|
|
1547
1585
|
type: "complete",
|
|
@@ -1549,7 +1587,8 @@ async function handleScanStream() {
|
|
|
1549
1587
|
added: added.length,
|
|
1550
1588
|
renamed: renamed.length,
|
|
1551
1589
|
errors: errors.length,
|
|
1552
|
-
renamedFiles: renamed
|
|
1590
|
+
renamedFiles: renamed,
|
|
1591
|
+
orphanedFiles: orphanedFiles.length > 0 ? orphanedFiles : void 0
|
|
1553
1592
|
});
|
|
1554
1593
|
} catch (error) {
|
|
1555
1594
|
console.error("Scan failed:", error);
|
|
@@ -1567,6 +1606,63 @@ async function handleScanStream() {
|
|
|
1567
1606
|
}
|
|
1568
1607
|
});
|
|
1569
1608
|
}
|
|
1609
|
+
async function handleDeleteOrphans(request) {
|
|
1610
|
+
try {
|
|
1611
|
+
const { paths } = await request.json();
|
|
1612
|
+
if (!paths || !Array.isArray(paths) || paths.length === 0) {
|
|
1613
|
+
return _server.NextResponse.json({ error: "No paths provided" }, { status: 400 });
|
|
1614
|
+
}
|
|
1615
|
+
const deleted = [];
|
|
1616
|
+
const errors = [];
|
|
1617
|
+
for (const orphanPath of paths) {
|
|
1618
|
+
if (!orphanPath.startsWith("/images/")) {
|
|
1619
|
+
errors.push(`Invalid path: ${orphanPath}`);
|
|
1620
|
+
continue;
|
|
1621
|
+
}
|
|
1622
|
+
const fullPath = _path2.default.join(process.cwd(), "public", orphanPath);
|
|
1623
|
+
try {
|
|
1624
|
+
await _fs.promises.unlink(fullPath);
|
|
1625
|
+
deleted.push(orphanPath);
|
|
1626
|
+
} catch (err) {
|
|
1627
|
+
console.error(`Failed to delete ${orphanPath}:`, err);
|
|
1628
|
+
errors.push(orphanPath);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
const imagesDir = _path2.default.join(process.cwd(), "public", "images");
|
|
1632
|
+
async function removeEmptyDirs(dir) {
|
|
1633
|
+
try {
|
|
1634
|
+
const entries = await _fs.promises.readdir(dir, { withFileTypes: true });
|
|
1635
|
+
let isEmpty = true;
|
|
1636
|
+
for (const entry of entries) {
|
|
1637
|
+
if (entry.isDirectory()) {
|
|
1638
|
+
const subDirEmpty = await removeEmptyDirs(_path2.default.join(dir, entry.name));
|
|
1639
|
+
if (!subDirEmpty) isEmpty = false;
|
|
1640
|
+
} else {
|
|
1641
|
+
isEmpty = false;
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
if (isEmpty && dir !== imagesDir) {
|
|
1645
|
+
await _fs.promises.rmdir(dir);
|
|
1646
|
+
}
|
|
1647
|
+
return isEmpty;
|
|
1648
|
+
} catch (e38) {
|
|
1649
|
+
return true;
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
try {
|
|
1653
|
+
await removeEmptyDirs(imagesDir);
|
|
1654
|
+
} catch (e39) {
|
|
1655
|
+
}
|
|
1656
|
+
return _server.NextResponse.json({
|
|
1657
|
+
success: true,
|
|
1658
|
+
deleted: deleted.length,
|
|
1659
|
+
errors: errors.length
|
|
1660
|
+
});
|
|
1661
|
+
} catch (error) {
|
|
1662
|
+
console.error("Failed to delete orphans:", error);
|
|
1663
|
+
return _server.NextResponse.json({ error: "Failed to delete orphaned files" }, { status: 500 });
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1570
1666
|
|
|
1571
1667
|
// src/handlers/import.ts
|
|
1572
1668
|
|
|
@@ -1754,6 +1850,9 @@ async function POST(request) {
|
|
|
1754
1850
|
if (route === "scan") {
|
|
1755
1851
|
return handleScanStream();
|
|
1756
1852
|
}
|
|
1853
|
+
if (route === "delete-orphans") {
|
|
1854
|
+
return handleDeleteOrphans(request);
|
|
1855
|
+
}
|
|
1757
1856
|
if (route === "import") {
|
|
1758
1857
|
return handleImportUrls(request);
|
|
1759
1858
|
}
|