@gallop.software/studio 2.3.147 → 2.3.148

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/server/index.ts","../../src/handlers/list.ts","../../src/handlers/utils/meta.ts","../../src/config/workspace.ts","../../src/handlers/utils/files.ts","../../src/handlers/utils/thumbnails.ts","../../src/handlers/utils/cdn.ts","../../src/types.ts","../../src/handlers/utils/response.ts","../../src/handlers/files.ts","../../src/handlers/utils/folders.ts","../../src/handlers/images.ts","../../src/handlers/scan.ts","../../src/handlers/import.ts","../../src/handlers/favicon.ts","../../src/handlers/featured-image.ts","../../src/handlers/edit-image.ts","../../src/handlers/fonts.ts"],"sourcesContent":["import express, { Request, Response } from \"express\";\nimport { resolve, join } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { dirname } from \"path\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { config as loadEnv } from \"dotenv\";\nimport { createServer } from \"net\";\n\n// Import handlers from individual modules\nimport {\n handleList,\n handleSearch,\n handleListFolders,\n handleCountImages,\n handleFolderImages,\n} from \"../handlers/list\";\nimport {\n handleUpload,\n handleDelete,\n handleDeleteStream,\n handleCreateFolder,\n handleRename,\n handleRenameStream,\n handleMoveStream,\n} from \"../handlers/files\";\nimport {\n handleSync,\n handleSyncStream,\n handleReprocessStream,\n handleUnprocessStream,\n handleDownloadStream,\n handlePushUpdatesStream,\n handleCancelUpdates,\n handleCancelStreamOperation,\n} from \"../handlers/images\";\nimport { handleScanStream, handleDeleteOrphans } from \"../handlers/scan\";\nimport {\n handleImportUrls,\n handleGetCdns,\n handleUpdateCdns,\n} from \"../handlers/import\";\nimport { handleGenerateFavicon } from \"../handlers/favicon\";\nimport {\n handleGenerateFeaturedImage,\n handleCheckFeaturedImage,\n handleGetFeaturedImageOptions,\n} from \"../handlers/featured-image\";\nimport { handleEditImage } from \"../handlers/edit-image\";\nimport { handleFontsList, handleFontsUpload, handleFontsCreateFolder, handleFontsDelete, handleFontsRename } from \"../handlers/fonts\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n// Get version from package.json\nconst packageJsonPath = resolve(__dirname, \"../../package.json\");\nconst packageJson = JSON.parse(readFileSync(packageJsonPath, \"utf-8\"));\nconst version = packageJson.version;\n\nexport interface ServerOptions {\n port: number;\n workspace: string;\n open?: boolean;\n}\n\n/**\n * Check if a port is available\n */\nfunction isPortAvailable(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const server = createServer();\n server.once(\"error\", () => {\n resolve(false);\n });\n server.once(\"listening\", () => {\n server.close();\n resolve(true);\n });\n server.listen(port);\n });\n}\n\n/**\n * Find an available port starting from the given port\n */\nasync function findAvailablePort(\n startPort: number,\n maxAttempts = 10\n): Promise<number> {\n for (let i = 0; i < maxAttempts; i++) {\n const port = startPort + i;\n if (await isPortAvailable(port)) {\n return port;\n }\n }\n throw new Error(\n `No available port found between ${startPort} and ${\n startPort + maxAttempts - 1\n }`\n );\n}\n\nexport async function startServer(options: ServerOptions) {\n const { port: requestedPort, workspace, open } = options;\n\n // Find an available port starting from the requested port\n const port = await findAvailablePort(requestedPort);\n if (port !== requestedPort) {\n console.log(`Port ${requestedPort} is in use, using port ${port} instead`);\n }\n\n const app = express();\n\n // Store workspace in a way handlers can access\n process.env.STUDIO_WORKSPACE = workspace;\n\n // Load environment variables from workspace\n // Load .env.local (contains both R2 credentials and local dev URL)\n const envLocalPath = join(workspace, \".env.local\");\n\n if (existsSync(envLocalPath)) {\n loadEnv({ path: envLocalPath, quiet: true });\n }\n\n // Also set STUDIO_DEV_SITE_URL from NEXT_PUBLIC_PRODUCTION_URL if not already set\n if (\n !process.env.STUDIO_DEV_SITE_URL &&\n process.env.NEXT_PUBLIC_PRODUCTION_URL\n ) {\n process.env.STUDIO_DEV_SITE_URL = process.env.NEXT_PUBLIC_PRODUCTION_URL;\n }\n\n // Middleware - skip JSON parsing for upload routes (needs raw body for FormData)\n const rawBodyPaths = [\"/api/studio/upload\", \"/api/studio/fonts/upload\"];\n app.use((req, res, next) => {\n if (rawBodyPaths.includes(req.path)) {\n next();\n } else {\n express.json({ limit: \"50mb\" })(req, res, next);\n }\n });\n app.use((req, res, next) => {\n if (rawBodyPaths.includes(req.path)) {\n next();\n } else {\n express.urlencoded({ extended: true, limit: \"50mb\" })(req, res, next);\n }\n });\n\n // API Routes - GET endpoints\n app.get(\"/api/studio/list\", wrapHandler(handleList));\n app.get(\"/api/studio/list-folders\", wrapHandler(handleListFolders));\n app.get(\"/api/studio/search\", wrapHandler(handleSearch));\n app.get(\"/api/studio/count-images\", wrapHandler(handleCountImages));\n app.get(\"/api/studio/folder-images\", wrapHandler(handleFolderImages));\n app.get(\"/api/studio/cdns\", wrapHandler(handleGetCdns));\n app.get(\n \"/api/studio/check-featured-image\",\n wrapHandler(handleCheckFeaturedImage)\n );\n app.get(\n \"/api/studio/featured-image-options\",\n wrapHandler(handleGetFeaturedImageOptions)\n );\n\n // Font management routes\n app.get(\"/api/studio/fonts/list\", wrapHandler(handleFontsList));\n app.post(\"/api/studio/fonts/upload\", wrapRawHandler(handleFontsUpload));\n app.post(\"/api/studio/fonts/create-folder\", wrapHandler(handleFontsCreateFolder));\n app.post(\"/api/studio/fonts/delete\", wrapHandler(handleFontsDelete));\n app.post(\"/api/studio/fonts/rename\", wrapHandler(handleFontsRename));\n\n // API Routes - POST endpoints\n // Upload uses raw body wrapper to preserve FormData\n app.post(\"/api/studio/upload\", wrapRawHandler(handleUpload));\n app.post(\"/api/studio/create-folder\", wrapHandler(handleCreateFolder));\n app.post(\"/api/studio/rename\", wrapHandler(handleRename));\n app.post(\"/api/studio/rename-stream\", wrapHandler(handleRenameStream, true));\n app.post(\"/api/studio/move\", wrapHandler(handleMoveStream, true));\n app.post(\"/api/studio/edit-image\", wrapHandler(handleEditImage));\n app.post(\"/api/studio/sync\", wrapHandler(handleSync, true));\n app.post(\"/api/studio/sync-stream\", wrapHandler(handleSyncStream, true));\n app.post(\n \"/api/studio/reprocess-stream\",\n wrapHandler(handleReprocessStream, true)\n );\n app.post(\n \"/api/studio/unprocess-stream\",\n wrapHandler(handleUnprocessStream, true)\n );\n app.post(\n \"/api/studio/download-stream\",\n wrapHandler(handleDownloadStream, true)\n );\n app.post(\n \"/api/studio/push-updates-stream\",\n wrapHandler(handlePushUpdatesStream, true)\n );\n app.post(\"/api/studio/cancel-updates\", wrapHandler(handleCancelUpdates));\n app.post(\n \"/api/studio/cancel-stream\",\n wrapHandler(handleCancelStreamOperation)\n );\n app.post(\"/api/studio/scan\", wrapHandler(handleScanStream, true));\n app.post(\"/api/studio/delete-orphans\", wrapHandler(handleDeleteOrphans));\n app.post(\"/api/studio/import\", wrapHandler(handleImportUrls, true));\n app.post(\"/api/studio/cdns\", wrapHandler(handleUpdateCdns));\n app.post(\n \"/api/studio/generate-favicon\",\n wrapHandler(handleGenerateFavicon, true)\n );\n app.post(\n \"/api/studio/generate-featured-image\",\n wrapHandler(handleGenerateFeaturedImage, true)\n );\n\n // API Routes - DELETE endpoints\n app.post(\"/api/studio/delete\", wrapHandler(handleDelete));\n app.post(\"/api/studio/delete-stream\", wrapHandler(handleDeleteStream, true));\n\n // Serve static files from workspace's public folder\n // Files are accessed at root path (e.g., /favicon.png, /images/photo.jpg)\n app.use(express.static(join(workspace, \"public\")));\n\n // Serve the client app\n const clientDir = resolve(__dirname, \"../client\");\n\n // Inject workspace and dev URL into the HTML\n app.get(\"/\", (req: Request, res: Response) => {\n const htmlPath = join(clientDir, \"index.html\");\n if (existsSync(htmlPath)) {\n let html = readFileSync(htmlPath, \"utf-8\");\n // Inject workspace and site URL as global variables\n const siteUrl = process.env.STUDIO_DEV_SITE_URL || \"\";\n const script = `<script>\n window.__STUDIO_WORKSPACE__ = ${JSON.stringify(workspace)};\n window.__STUDIO_SITE_URL__ = ${JSON.stringify(siteUrl)};\n </script>`;\n html = html.replace(\"</head>\", `${script}</head>`);\n res.type(\"html\").send(html);\n } else {\n res.status(404).send(\"Client not built. Run npm run build first.\");\n }\n });\n\n // Serve other static assets\n app.use(express.static(clientDir));\n\n // Start server\n const title = `Gallop - Studio (${version})`;\n app.listen(port, () => {\n console.log(`\n┌─────────────────────────────────────┐\n│ ${title.padEnd(34)}│\n├─────────────────────────────────────┤\n│ Workspace: ${\n workspace.length > 24\n ? \"...\" + workspace.slice(-21)\n : workspace.padEnd(24)\n }│\n│ URL: http://localhost:${port} │\n└─────────────────────────────────────┘\n`);\n\n if (open) {\n import(\"open\")\n .then((mod) => {\n mod.default(`http://localhost:${port}`);\n })\n .catch(() => {\n // open package might not be available\n });\n }\n });\n}\n\n// Wrapper to adapt Next.js-style handlers to Express\nfunction wrapHandler(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handler: (request?: any) => Promise<globalThis.Response>,\n streaming = false\n) {\n return async (req: Request, res: Response) => {\n try {\n const request = createFetchRequest(req);\n const response = await handler(request);\n if (streaming) {\n await sendStreamingResponse(res, response);\n } else {\n await sendResponse(res, response);\n }\n } catch (error) {\n console.error(\"Handler error:\", error);\n res.status(500).json({ error: \"Internal server error\" });\n }\n };\n}\n\n// Wrapper for handlers that need raw body (like file uploads with FormData)\nfunction wrapRawHandler(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handler: (request?: any) => Promise<globalThis.Response>\n) {\n return async (req: Request, res: Response) => {\n try {\n const request = await createRawFetchRequest(req);\n const response = await handler(request);\n await sendResponse(res, response);\n } catch (error) {\n console.error(\"Handler error:\", error);\n res.status(500).json({ error: \"Internal server error\" });\n }\n };\n}\n\n// Helper to create a Fetch API Request from Express request\nfunction createFetchRequest(req: Request): globalThis.Request {\n const url = new URL(req.url, `http://${req.headers.host}`);\n\n const headers = new Headers();\n for (const [key, value] of Object.entries(req.headers)) {\n if (value) {\n if (Array.isArray(value)) {\n value.forEach((v) => headers.append(key, v));\n } else {\n headers.set(key, value);\n }\n }\n }\n\n const init: RequestInit = {\n method: req.method,\n headers,\n };\n\n // Add body for non-GET requests\n if (req.method !== \"GET\" && req.method !== \"HEAD\") {\n if (req.body) {\n init.body = JSON.stringify(req.body);\n }\n }\n\n return new globalThis.Request(url.toString(), init);\n}\n\n// Helper to create a Fetch API Request with raw body (for FormData uploads)\nasync function createRawFetchRequest(\n req: Request\n): Promise<globalThis.Request> {\n const url = new URL(req.url, `http://${req.headers.host}`);\n\n const headers = new Headers();\n for (const [key, value] of Object.entries(req.headers)) {\n if (value) {\n if (Array.isArray(value)) {\n value.forEach((v) => headers.append(key, v));\n } else {\n headers.set(key, value);\n }\n }\n }\n\n // Collect raw body chunks\n const chunks: Buffer[] = [];\n for await (const chunk of req) {\n chunks.push(Buffer.from(chunk));\n }\n const body = Buffer.concat(chunks);\n\n const init: RequestInit = {\n method: req.method,\n headers,\n body,\n };\n\n return new globalThis.Request(url.toString(), init);\n}\n\n// Helper to send a Response to Express response\nasync function sendResponse(res: Response, response: globalThis.Response) {\n res.status(response.status);\n\n // Copy headers\n response.headers.forEach((value, key) => {\n res.setHeader(key, value);\n });\n\n // Send body\n const body = await response.text();\n res.send(body);\n}\n\n// Helper to send a streaming Response to Express response\nasync function sendStreamingResponse(\n res: Response,\n response: globalThis.Response\n) {\n res.status(response.status);\n\n // Copy headers\n response.headers.forEach((value, key) => {\n res.setHeader(key, value);\n });\n\n // Check if it's a streaming response\n if (response.body) {\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n res.write(decoder.decode(value, { stream: true }));\n }\n res.end();\n } catch (error) {\n console.error(\"Streaming error:\", error);\n res.end();\n }\n } else {\n const body = await response.text();\n res.send(body);\n }\n}\n","import { promises as fs } from \"fs\";\nimport path from \"path\";\nimport type { FileItem, MetaEntry } from \"../types\";\nimport { loadMeta, isImageFile, getCdnUrls, getFileEntries } from \"./utils\";\nimport { getThumbnailPath, isProcessed } from \"../types\";\nimport { getPublicPath, getWorkspacePath } from \"../config\";\nimport { jsonResponse } from \"./utils/response\";\n\n/**\n * Get all thumbnail file info for a processed meta entry\n * Returns the thumbnail paths that exist based on which dimension properties are present\n */\nfunction getExistingThumbnails(\n originalPath: string,\n entry: MetaEntry\n): Array<{ path: string; size: \"f\" | \"lg\" | \"md\" | \"sm\" }> {\n const thumbnails: Array<{ path: string; size: \"f\" | \"lg\" | \"md\" | \"sm\" }> =\n [];\n\n if (entry.f) {\n thumbnails.push({\n path: getThumbnailPath(originalPath, \"full\"),\n size: \"f\",\n });\n }\n if (entry.lg) {\n thumbnails.push({ path: getThumbnailPath(originalPath, \"lg\"), size: \"lg\" });\n }\n if (entry.md) {\n thumbnails.push({ path: getThumbnailPath(originalPath, \"md\"), size: \"md\" });\n }\n if (entry.sm) {\n thumbnails.push({ path: getThumbnailPath(originalPath, \"sm\"), size: \"sm\" });\n }\n\n return thumbnails;\n}\n\n/**\n * Count cloud, remote, and local files for a folder prefix\n */\nfunction countFileTypes(\n folderPrefix: string,\n fileEntries: [string, MetaEntry][],\n cdnUrls: string[],\n r2PublicUrl: string\n): {\n cloudCount: number;\n remoteCount: number;\n localCount: number;\n updateCount: number;\n} {\n let cloudCount = 0;\n let remoteCount = 0;\n let localCount = 0;\n let updateCount = 0;\n\n for (const [key, entry] of fileEntries) {\n if (key.startsWith(folderPrefix)) {\n if (entry.c !== undefined) {\n // Check if it's our R2 or a remote URL (normalize trailing slashes)\n const cdnUrl = cdnUrls[entry.c]?.replace(/\\/$/, \"\") || \"\";\n if (cdnUrl === r2PublicUrl) {\n cloudCount++;\n } else {\n remoteCount++;\n }\n } else {\n localCount++;\n }\n // Count pending updates\n if (entry.u === 1) {\n updateCount++;\n }\n }\n }\n\n return { cloudCount, remoteCount, localCount, updateCount };\n}\n\n/**\n * List files and folders from meta\n * Folders are derived from file paths in meta AND filesystem\n */\nexport async function handleList(request: Request) {\n const searchParams = new URL(request.url).searchParams;\n const requestedPath = searchParams.get(\"path\") || \"public\";\n\n try {\n const meta = await loadMeta();\n const fileEntries = getFileEntries(meta);\n const cdnUrls = getCdnUrls(meta);\n const r2PublicUrl =\n process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/$/, \"\") || \"\";\n\n // Normalize the requested path to match meta keys\n // requestedPath is like \"public\" or \"public/photos\"\n // meta keys are like \"/photos/image.jpg\"\n const relativePath = requestedPath.replace(/^public\\/?/, \"\");\n const pathPrefix = relativePath ? `/${relativePath}/` : \"/\";\n\n const items: FileItem[] = [];\n const seenFolders = new Set<string>();\n const metaKeys = fileEntries.map(([key]) => key);\n\n // Check if we're inside the images folder (protected area)\n const isInsideImagesFolder =\n relativePath === \"images\" || relativePath.startsWith(\"images/\");\n\n // For the images folder, derive contents from meta entries with thumbnails\n if (isInsideImagesFolder) {\n // Get the path within images folder (e.g., \"images/subfolder\" -> \"subfolder\")\n const imagesSubPath = relativePath.replace(/^images\\/?/, \"\");\n const imagesPrefix = imagesSubPath ? `/${imagesSubPath}/` : \"/\";\n\n // Collect all thumbnails from processed entries\n const allThumbnails: Array<{\n path: string;\n size: \"f\" | \"lg\" | \"md\" | \"sm\";\n originalKey: string;\n }> = [];\n\n for (const [key, entry] of fileEntries) {\n if (isProcessed(entry)) {\n const thumbnails = getExistingThumbnails(key, entry);\n for (const thumb of thumbnails) {\n allThumbnails.push({ ...thumb, originalKey: key });\n }\n }\n }\n\n // Filter thumbnails that are in the current images subfolder\n for (const thumb of allThumbnails) {\n // thumb.path is like \"/images/photos/image.jpg\" or \"/images/photos/image-lg.jpg\"\n // We need to check if it's under the current images path\n const thumbRelative = thumb.path.replace(/^\\/images\\/?/, \"\");\n\n // Get the original entry to check if it's on CDN\n const originalEntry = fileEntries.find(\n ([k]) => k === thumb.originalKey\n )?.[1];\n const cdnIndex = originalEntry?.c;\n const cdnBaseUrl =\n cdnIndex !== undefined ? cdnUrls[cdnIndex] : undefined;\n // Build the full thumbnail URL (with CDN base if applicable)\n const thumbnailUrl = cdnBaseUrl\n ? `${cdnBaseUrl}${thumb.path}`\n : thumb.path;\n // Determine if it's pushed to CDN and if it's remote (not our R2)\n const isPushedToCloud = cdnIndex !== undefined;\n const normalizedCdnBaseUrl = cdnBaseUrl?.replace(/\\/$/, \"\") || \"\";\n const isRemote =\n isPushedToCloud && normalizedCdnBaseUrl !== r2PublicUrl;\n\n // Get dimensions for this thumbnail size\n const thumbDims = originalEntry?.[thumb.size];\n const dimensions = thumbDims\n ? { width: thumbDims.w, height: thumbDims.h }\n : undefined;\n\n // Check if this is directly in the current folder or in a subfolder\n if (imagesSubPath === \"\") {\n // We're at /images root\n const slashIndex = thumbRelative.indexOf(\"/\");\n if (slashIndex === -1) {\n // Direct file in images root\n const fileName = thumbRelative;\n items.push({\n name: fileName,\n path: `public/images/${fileName}`,\n type: \"file\",\n thumbnail: thumbnailUrl,\n hasThumbnail: false,\n isProtected: true,\n cdnPushed: isPushedToCloud,\n cdnBaseUrl,\n isCloud: isPushedToCloud && !isRemote,\n isRemote,\n dimensions,\n });\n } else {\n // In a subfolder - add the folder\n const folderName = thumbRelative.slice(0, slashIndex);\n if (!seenFolders.has(folderName)) {\n seenFolders.add(folderName);\n // Count thumbnails in this folder with cloud/local breakdown\n const folderPrefix = `/${folderName}/`;\n const folderThumbs = allThumbnails.filter((t) =>\n t.path.replace(/^\\/images/, \"\").startsWith(folderPrefix)\n );\n let folderCloudCount = 0;\n let folderRemoteCount = 0;\n let folderLocalCount = 0;\n for (const ft of folderThumbs) {\n const origEntry = meta[ft.originalKey] as MetaEntry | undefined;\n if (origEntry?.c !== undefined) {\n const entryCdnUrl = cdnUrls[origEntry.c]?.replace(/\\/?$/, \"\");\n if (r2PublicUrl && entryCdnUrl === r2PublicUrl) {\n folderCloudCount++;\n } else {\n folderRemoteCount++;\n }\n } else {\n folderLocalCount++;\n }\n }\n items.push({\n name: folderName,\n path: `public/images/${folderName}`,\n type: \"folder\",\n fileCount: folderThumbs.length,\n cloudCount: folderCloudCount,\n remoteCount: folderRemoteCount,\n localCount: folderLocalCount,\n isProtected: true,\n });\n }\n }\n } else {\n // We're in a subfolder of images\n if (\n !thumbRelative.startsWith(imagesSubPath + \"/\") &&\n thumbRelative !== imagesSubPath\n )\n continue;\n\n const remaining = thumbRelative.slice(imagesSubPath.length + 1);\n if (!remaining) continue;\n\n const slashIndex = remaining.indexOf(\"/\");\n if (slashIndex === -1) {\n // Direct file\n items.push({\n name: remaining,\n path: `public/images/${imagesSubPath}/${remaining}`,\n type: \"file\",\n thumbnail: thumbnailUrl,\n hasThumbnail: false,\n isProtected: true,\n cdnPushed: isPushedToCloud,\n cdnBaseUrl,\n isCloud: isPushedToCloud && !isRemote,\n isRemote,\n dimensions,\n });\n } else {\n // Subfolder\n const folderName = remaining.slice(0, slashIndex);\n if (!seenFolders.has(folderName)) {\n seenFolders.add(folderName);\n const folderPrefix = `${imagesSubPath}/${folderName}/`;\n const folderThumbs = allThumbnails.filter((t) =>\n t.path.replace(/^\\/images\\//, \"\").startsWith(folderPrefix)\n );\n let subCloudCount = 0;\n let subRemoteCount = 0;\n let subLocalCount = 0;\n for (const ft of folderThumbs) {\n const origEntry = meta[ft.originalKey] as MetaEntry | undefined;\n if (origEntry?.c !== undefined) {\n const entryCdnUrl = cdnUrls[origEntry.c]?.replace(/\\/?$/, \"\");\n if (r2PublicUrl && entryCdnUrl === r2PublicUrl) {\n subCloudCount++;\n } else {\n subRemoteCount++;\n }\n } else {\n subLocalCount++;\n }\n }\n items.push({\n name: folderName,\n path: `public/images/${imagesSubPath}/${folderName}`,\n type: \"folder\",\n fileCount: folderThumbs.length,\n cloudCount: subCloudCount,\n remoteCount: subRemoteCount,\n localCount: subLocalCount,\n isProtected: true,\n });\n }\n }\n }\n }\n\n return jsonResponse({ items });\n }\n\n // Not in images folder - check filesystem for folders (including empty ones)\n const absoluteDir = getWorkspacePath(requestedPath);\n try {\n const dirEntries = await fs.readdir(absoluteDir, { withFileTypes: true });\n for (const entry of dirEntries) {\n if (entry.name.startsWith(\".\")) continue;\n\n if (entry.isDirectory()) {\n if (!seenFolders.has(entry.name)) {\n seenFolders.add(entry.name);\n\n // Check if this folder is the images folder\n const isImagesFolder = entry.name === \"images\" && !relativePath;\n const folderPath = relativePath\n ? `public/${relativePath}/${entry.name}`\n : `public/${entry.name}`;\n\n // Count files in this folder\n let fileCount = 0;\n let cloudCount = 0;\n let remoteCount = 0;\n let localCount = 0;\n let updateCount = 0;\n\n if (isImagesFolder) {\n // Count thumbnails from meta for images folder\n for (const [key, metaEntry] of fileEntries) {\n if (isProcessed(metaEntry)) {\n const thumbCount = getExistingThumbnails(\n key,\n metaEntry\n ).length;\n fileCount += thumbCount;\n // Thumbnails are on CDN if original is on CDN\n if (metaEntry.c !== undefined) {\n const entryCdnUrl = cdnUrls[metaEntry.c]?.replace(\n /\\/?$/,\n \"\"\n );\n if (r2PublicUrl && entryCdnUrl === r2PublicUrl) {\n cloudCount += thumbCount;\n } else {\n remoteCount += thumbCount;\n }\n } else {\n localCount += thumbCount;\n }\n }\n }\n } else {\n // Count files from meta for regular folders\n const folderPrefix =\n pathPrefix === \"/\"\n ? `/${entry.name}/`\n : `${pathPrefix}${entry.name}/`;\n for (const k of metaKeys) {\n if (k.startsWith(folderPrefix)) fileCount++;\n }\n // Count cloud vs remote vs local\n const counts = countFileTypes(\n folderPrefix,\n fileEntries,\n cdnUrls,\n r2PublicUrl\n );\n cloudCount = counts.cloudCount;\n remoteCount = counts.remoteCount;\n localCount = counts.localCount;\n updateCount = counts.updateCount;\n }\n\n items.push({\n name: entry.name,\n path: folderPath,\n type: \"folder\",\n fileCount,\n cloudCount,\n remoteCount,\n localCount,\n updateCount,\n isProtected: isImagesFolder,\n });\n }\n }\n }\n } catch {\n // Directory might not exist (all files in cloud)\n }\n\n // Always show images folder at root level if any processed images exist\n if (!relativePath && !seenFolders.has(\"images\")) {\n let thumbnailCount = 0;\n let imgCloudCount = 0;\n let imgRemoteCount = 0;\n let imgLocalCount = 0;\n for (const [key, entry] of fileEntries) {\n if (isProcessed(entry)) {\n const thumbCount = getExistingThumbnails(key, entry).length;\n thumbnailCount += thumbCount;\n // Thumbnails are on CDN if original is on CDN\n if (entry.c !== undefined) {\n const entryCdnUrl = cdnUrls[entry.c]?.replace(/\\/?$/, \"\");\n if (r2PublicUrl && entryCdnUrl === r2PublicUrl) {\n imgCloudCount += thumbCount;\n } else {\n imgRemoteCount += thumbCount;\n }\n } else {\n imgLocalCount += thumbCount;\n }\n }\n }\n if (thumbnailCount > 0) {\n items.push({\n name: \"images\",\n path: \"public/images\",\n type: \"folder\",\n fileCount: thumbnailCount,\n cloudCount: imgCloudCount,\n remoteCount: imgRemoteCount,\n localCount: imgLocalCount,\n isProtected: true,\n });\n }\n }\n\n // If meta is empty and no folders found, return empty with a flag\n if (fileEntries.length === 0 && items.length === 0) {\n return jsonResponse({ items: [], isEmpty: true });\n }\n\n for (const [key, entry] of fileEntries) {\n // Check if this file is under the current path\n if (!key.startsWith(pathPrefix) && pathPrefix !== \"/\") continue;\n if (pathPrefix === \"/\" && !key.startsWith(\"/\")) continue;\n\n // Get the part after the current path\n const remaining =\n pathPrefix === \"/\" ? key.slice(1) : key.slice(pathPrefix.length);\n\n // Skip if empty (shouldn't happen)\n if (!remaining) continue;\n\n // Check if there's a subfolder\n const slashIndex = remaining.indexOf(\"/\");\n\n if (slashIndex !== -1) {\n // This is in a subfolder - show the folder\n const folderName = remaining.slice(0, slashIndex);\n\n if (!seenFolders.has(folderName)) {\n seenFolders.add(folderName);\n\n // Count files in this folder from meta\n const folderPrefix =\n pathPrefix === \"/\"\n ? `/${folderName}/`\n : `${pathPrefix}${folderName}/`;\n let fileCount = 0;\n for (const k of metaKeys) {\n if (k.startsWith(folderPrefix)) fileCount++;\n }\n\n // Count cloud vs remote vs local\n const counts = countFileTypes(\n folderPrefix,\n fileEntries,\n cdnUrls,\n r2PublicUrl\n );\n\n items.push({\n name: folderName,\n path: relativePath\n ? `public/${relativePath}/${folderName}`\n : `public/${folderName}`,\n type: \"folder\",\n fileCount,\n cloudCount: counts.cloudCount,\n remoteCount: counts.remoteCount,\n localCount: counts.localCount,\n updateCount: counts.updateCount,\n isProtected: isInsideImagesFolder,\n });\n }\n } else {\n // This is a file in the current folder\n const fileName = remaining;\n const isImage = isImageFile(fileName);\n const isPushedToCloud = entry.c !== undefined;\n\n // Determine if this is a remote import vs pushed to our R2\n const fileCdnUrl =\n isPushedToCloud && entry.c !== undefined\n ? cdnUrls[entry.c]\n : undefined;\n const normalizedFileCdnUrl = fileCdnUrl?.replace(/\\/$/, \"\") || \"\";\n const isRemote =\n isPushedToCloud &&\n (!r2PublicUrl || normalizedFileCdnUrl !== r2PublicUrl);\n\n let thumbnail: string | undefined;\n let hasThumbnail = false;\n let fileSize: number | undefined;\n\n const entryIsProcessed = isProcessed(entry);\n\n if (isImage && entryIsProcessed) {\n // Has been processed - determine best available thumbnail\n // Prefer sm, then md, then lg, then full, then original\n const hasSm = !!entry.sm;\n const hasMd = !!entry.md;\n const hasLg = !!entry.lg;\n const hasFull = !!entry.f;\n\n let thumbSize: \"sm\" | \"md\" | \"lg\" | \"full\" | null = null;\n if (hasSm) thumbSize = \"sm\";\n else if (hasMd) thumbSize = \"md\";\n else if (hasLg) thumbSize = \"lg\";\n else if (hasFull) thumbSize = \"full\";\n\n if (thumbSize) {\n const thumbPath = getThumbnailPath(key, thumbSize);\n\n if (isPushedToCloud && entry.c !== undefined) {\n // CDN thumbnail - get URL from _cdns array\n const cdnUrl = cdnUrls[entry.c];\n if (cdnUrl) {\n thumbnail = `${cdnUrl}${thumbPath}`;\n hasThumbnail = true;\n }\n } else {\n // Local thumbnail - check if exists\n const localThumbPath = getPublicPath(thumbPath);\n try {\n await fs.access(localThumbPath);\n thumbnail = thumbPath;\n hasThumbnail = true;\n } catch {\n // Thumbnail doesn't exist yet, use original\n thumbnail = key;\n hasThumbnail = false;\n }\n }\n } else {\n // No thumbnails available, use original\n if (isPushedToCloud && entry.c !== undefined) {\n const cdnUrl = cdnUrls[entry.c];\n thumbnail = cdnUrl ? `${cdnUrl}${key}` : key;\n } else {\n thumbnail = key;\n }\n hasThumbnail = false;\n }\n } else if (isImage) {\n // Not processed yet - use original (from CDN if available)\n if (isPushedToCloud && entry.c !== undefined) {\n const cdnUrl = cdnUrls[entry.c];\n thumbnail = cdnUrl ? `${cdnUrl}${key}` : key;\n } else {\n thumbnail = key;\n }\n hasThumbnail = false;\n }\n\n // Try to get file size if file exists locally\n if (!isPushedToCloud) {\n try {\n const filePath = getPublicPath(key);\n const stats = await fs.stat(filePath);\n fileSize = stats.size;\n } catch {\n // File might not exist locally (synced)\n }\n }\n\n items.push({\n name: fileName,\n path: relativePath\n ? `public/${relativePath}/${fileName}`\n : `public/${fileName}`,\n type: \"file\",\n size: fileSize,\n thumbnail,\n hasThumbnail,\n isProcessed: entryIsProcessed,\n hasSm: !!entry.sm,\n hasMd: !!entry.md,\n hasLg: !!entry.lg,\n hasFull: !!entry.f,\n cdnPushed: isPushedToCloud,\n cdnBaseUrl: fileCdnUrl,\n isCloud: isPushedToCloud && !isRemote,\n isRemote,\n isProtected: isInsideImagesFolder,\n dimensions: entry.o\n ? { width: entry.o.w, height: entry.o.h }\n : undefined,\n hasUpdate: entry.u === 1,\n });\n }\n }\n\n return jsonResponse({ items });\n } catch (error) {\n console.error(\"Failed to list directory:\", error);\n return jsonResponse({ error: \"Failed to list directory\" }, { status: 500 });\n }\n}\n\nexport async function handleSearch(request: Request) {\n const searchParams = new URL(request.url).searchParams;\n const query = searchParams.get(\"q\")?.toLowerCase() || \"\";\n const requestedPath = searchParams.get(\"path\") || \"public\";\n\n if (query.length < 2) {\n return jsonResponse({ items: [] });\n }\n\n // Convert path to filter prefix (e.g. \"public/images\" -> \"/images/\")\n const pathPrefix =\n requestedPath === \"public\"\n ? \"/\"\n : \"/\" + requestedPath.replace(/^public\\/?/, \"\");\n const normalizedPrefix = pathPrefix.endsWith(\"/\")\n ? pathPrefix\n : pathPrefix + \"/\";\n\n try {\n const meta = await loadMeta();\n const fileEntries = getFileEntries(meta);\n const cdnUrls = getCdnUrls(meta);\n const r2PublicUrl =\n process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/$/, \"\") || \"\";\n const items: FileItem[] = [];\n\n for (const [key, entry] of fileEntries) {\n // Check if file is within the requested path (including subfolders)\n if (!key.startsWith(normalizedPrefix)) continue;\n\n // Check if the path matches the query\n if (!key.toLowerCase().includes(query)) continue;\n\n const fileName = path.basename(key);\n const relativePath = key.slice(1); // Remove leading /\n const isImage = isImageFile(fileName);\n const isPushedToCloud = entry.c !== undefined;\n\n // Determine if this is a remote import vs pushed to our R2\n const fileCdnUrl =\n isPushedToCloud && entry.c !== undefined ? cdnUrls[entry.c] : undefined;\n const normalizedFileCdnUrl = fileCdnUrl?.replace(/\\/$/, \"\") || \"\";\n const isRemote =\n isPushedToCloud &&\n (!r2PublicUrl || normalizedFileCdnUrl !== r2PublicUrl);\n\n let thumbnail: string | undefined;\n let hasThumbnail = false;\n const entryIsProcessed = isProcessed(entry);\n\n if (isImage && entryIsProcessed) {\n // Has been processed - determine best available thumbnail\n // Prefer sm, then md, then lg, then full, then original\n const hasSm = !!entry.sm;\n const hasMd = !!entry.md;\n const hasLg = !!entry.lg;\n const hasFull = !!entry.f;\n\n let thumbSize: \"sm\" | \"md\" | \"lg\" | \"full\" | null = null;\n if (hasSm) thumbSize = \"sm\";\n else if (hasMd) thumbSize = \"md\";\n else if (hasLg) thumbSize = \"lg\";\n else if (hasFull) thumbSize = \"full\";\n\n if (thumbSize) {\n const thumbPath = getThumbnailPath(key, thumbSize);\n\n if (isPushedToCloud && entry.c !== undefined) {\n const cdnUrl = cdnUrls[entry.c];\n if (cdnUrl) {\n thumbnail = `${cdnUrl}${thumbPath}`;\n hasThumbnail = true;\n }\n } else {\n const localThumbPath = getPublicPath(thumbPath);\n try {\n await fs.access(localThumbPath);\n thumbnail = thumbPath;\n hasThumbnail = true;\n } catch {\n thumbnail = key;\n hasThumbnail = false;\n }\n }\n } else {\n // No thumbnails available, use original\n if (isPushedToCloud && entry.c !== undefined) {\n const cdnUrl = cdnUrls[entry.c];\n thumbnail = cdnUrl ? `${cdnUrl}${key}` : key;\n } else {\n thumbnail = key;\n }\n hasThumbnail = false;\n }\n } else if (isImage) {\n // Not processed yet - use original (from CDN if available)\n if (isPushedToCloud && entry.c !== undefined) {\n const cdnUrl = cdnUrls[entry.c];\n thumbnail = cdnUrl ? `${cdnUrl}${key}` : key;\n } else {\n thumbnail = key;\n }\n hasThumbnail = false;\n }\n\n items.push({\n name: fileName,\n path: `public/${relativePath}`,\n type: \"file\",\n thumbnail,\n hasThumbnail,\n isProcessed: entryIsProcessed,\n hasSm: !!entry.sm,\n hasMd: !!entry.md,\n hasLg: !!entry.lg,\n hasFull: !!entry.f,\n cdnPushed: isPushedToCloud,\n cdnBaseUrl: fileCdnUrl,\n isCloud: isPushedToCloud && !isRemote,\n isRemote,\n dimensions: entry.o\n ? { width: entry.o.w, height: entry.o.h }\n : undefined,\n hasUpdate: entry.u === 1,\n });\n }\n\n return jsonResponse({ items });\n } catch (error) {\n console.error(\"Failed to search:\", error);\n return jsonResponse({ error: \"Failed to search\" }, { status: 500 });\n }\n}\n\nexport async function handleListFolders() {\n try {\n const meta = await loadMeta();\n const fileEntries = getFileEntries(meta);\n const folderSet = new Set<string>();\n\n // Extract all folder paths from meta keys\n for (const [key] of fileEntries) {\n const parts = key.split(\"/\");\n // Build up folder paths: /photos/2024/image.jpg -> photos, photos/2024\n let current = \"\";\n for (let i = 1; i < parts.length - 1; i++) {\n current = current ? `${current}/${parts[i]}` : parts[i];\n folderSet.add(current);\n }\n }\n\n // Also scan filesystem recursively for folders (including empty ones)\n async function scanDir(dir: string, relativePath: string): Promise<void> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (\n entry.isDirectory() &&\n !entry.name.startsWith(\".\") &&\n entry.name !== \"images\"\n ) {\n const folderRelPath = relativePath\n ? `${relativePath}/${entry.name}`\n : entry.name;\n folderSet.add(folderRelPath);\n // Recurse into subdirectory\n await scanDir(path.join(dir, entry.name), folderRelPath);\n }\n }\n } catch {\n // Directory might not exist\n }\n }\n\n const publicDir = getPublicPath();\n await scanDir(publicDir, \"\");\n\n const folders: { path: string; name: string; depth: number }[] = [];\n folders.push({ path: \"public\", name: \"public\", depth: 0 });\n\n const sortedFolders = Array.from(folderSet).sort();\n for (const folderPath of sortedFolders) {\n const depth = folderPath.split(\"/\").length;\n const name = folderPath.split(\"/\").pop() || folderPath;\n folders.push({\n path: `public/${folderPath}`,\n name,\n depth,\n });\n }\n\n return jsonResponse({ folders });\n } catch (error) {\n console.error(\"Failed to list folders:\", error);\n return jsonResponse({ error: \"Failed to list folders\" }, { status: 500 });\n }\n}\n\nexport async function handleCountImages() {\n try {\n const meta = await loadMeta();\n const fileEntries = getFileEntries(meta);\n const allImages: string[] = [];\n\n for (const [key] of fileEntries) {\n const fileName = path.basename(key);\n if (isImageFile(fileName)) {\n allImages.push(key.slice(1)); // Remove leading /\n }\n }\n\n return jsonResponse({\n count: allImages.length,\n images: allImages,\n });\n } catch (error) {\n console.error(\"Failed to count images:\", error);\n return jsonResponse({ error: \"Failed to count images\" }, { status: 500 });\n }\n}\n\nexport async function handleFolderImages(request: Request) {\n try {\n const searchParams = new URL(request.url).searchParams;\n const foldersParam = searchParams.get(\"folders\");\n\n if (!foldersParam) {\n return jsonResponse({ error: \"No folders provided\" }, { status: 400 });\n }\n\n const folders = foldersParam.split(\",\");\n const meta = await loadMeta();\n const fileEntries = getFileEntries(meta);\n const allFiles: string[] = [];\n\n // Convert folder paths to prefixes for matching\n const prefixes = folders.map((f) => {\n const rel = f.replace(/^public\\/?/, \"\");\n return rel ? `/${rel}/` : \"/\";\n });\n\n for (const [key] of fileEntries) {\n // Check if this file is in one of the requested folders\n for (const prefix of prefixes) {\n if (key.startsWith(prefix) || (prefix === \"/\" && key.startsWith(\"/\"))) {\n allFiles.push(key.slice(1)); // Remove leading /\n break;\n }\n }\n }\n\n return jsonResponse({\n count: allFiles.length,\n images: allFiles, // Keep as 'images' for backwards compatibility\n });\n } catch (error) {\n console.error(\"Failed to get folder files:\", error);\n return jsonResponse(\n { error: \"Failed to get folder files\" },\n { status: 500 }\n );\n }\n}\n","import { promises as fs } from 'fs'\nimport path from 'path'\nimport type { FullMeta, MetaEntry } from '../../types'\nimport { getDataPath } from '../../config'\n\nexport async function loadMeta(): Promise<FullMeta> {\n const metaPath = getDataPath('_studio.json')\n \n try {\n const content = await fs.readFile(metaPath, 'utf-8')\n return JSON.parse(content) as FullMeta\n } catch {\n return {}\n }\n}\n\nexport async function saveMeta(meta: FullMeta): Promise<void> {\n const dataDir = getDataPath()\n await fs.mkdir(dataDir, { recursive: true })\n const metaPath = getDataPath('_studio.json')\n \n // Ensure _cdns is at the top by creating ordered object\n const ordered: FullMeta = {}\n if (meta._cdns) {\n ordered._cdns = meta._cdns\n }\n // Add all other entries\n for (const [key, value] of Object.entries(meta)) {\n if (key !== '_cdns') {\n ordered[key] = value\n }\n }\n \n await fs.writeFile(metaPath, JSON.stringify(ordered, null, 2))\n}\n\n/**\n * Get the CDN URLs array from meta\n */\nexport function getCdnUrls(meta: FullMeta): string[] {\n return meta._cdns || []\n}\n\n/**\n * Set the CDN URLs array in meta\n */\nexport function setCdnUrls(meta: FullMeta, urls: string[]): void {\n meta._cdns = urls\n}\n\n/**\n * Get or add a CDN URL, returning its index\n */\nexport function getOrAddCdnIndex(meta: FullMeta, cdnUrl: string): number {\n if (!meta._cdns) {\n meta._cdns = []\n }\n \n // Normalize URL (remove trailing slash)\n const normalizedUrl = cdnUrl.replace(/\\/$/, '')\n \n const existingIndex = meta._cdns.indexOf(normalizedUrl)\n if (existingIndex >= 0) {\n return existingIndex\n }\n \n // Add new CDN URL\n meta._cdns.push(normalizedUrl)\n return meta._cdns.length - 1\n}\n\n/**\n * Get a meta entry (excludes special keys like _cdns)\n */\nexport function getMetaEntry(meta: FullMeta, key: string): MetaEntry | undefined {\n if (key.startsWith('_')) return undefined\n const value = meta[key]\n if (Array.isArray(value)) return undefined\n return value as MetaEntry | undefined\n}\n\n/**\n * Set a meta entry\n */\nexport function setMetaEntry(meta: FullMeta, key: string, entry: MetaEntry): void {\n meta[key] = entry\n}\n\n/**\n * Delete a meta entry\n */\nexport function deleteMetaEntry(meta: FullMeta, key: string): void {\n delete meta[key]\n}\n\n/**\n * Get all file entries (excludes special keys like _cdns)\n */\nexport function getFileEntries(meta: FullMeta): Array<[string, MetaEntry]> {\n return Object.entries(meta).filter(\n ([key, value]) => !key.startsWith('_') && !Array.isArray(value)\n ) as Array<[string, MetaEntry]>\n}\n","import path from 'path'\n\nlet workspacePath: string | null = null\n\n/**\n * Get the workspace root path.\n * In standalone mode, this comes from STUDIO_WORKSPACE env var.\n * In embedded mode, this defaults to process.cwd().\n */\nexport function getWorkspace(): string {\n if (workspacePath === null) {\n workspacePath = process.env.STUDIO_WORKSPACE || process.cwd()\n }\n return workspacePath\n}\n\n/**\n * Get an absolute path within the public folder.\n */\nexport function getPublicPath(...segments: string[]): string {\n return path.join(getWorkspace(), 'public', ...segments)\n}\n\n/**\n * Get an absolute path within the _data folder.\n */\nexport function getDataPath(...segments: string[]): string {\n return path.join(getWorkspace(), '_data', ...segments)\n}\n\n/**\n * Get an absolute path within the src/app folder.\n */\nexport function getSrcAppPath(...segments: string[]): string {\n return path.join(getWorkspace(), 'src', 'app', ...segments)\n}\n\n/**\n * Get an absolute path within the workspace root.\n */\nexport function getWorkspacePath(...segments: string[]): string {\n return path.join(getWorkspace(), ...segments)\n}\n","import { promises as fs } from 'fs'\nimport path from 'path'\n\n/**\n * Convert a filename to a slug-friendly format:\n * - lowercase\n * - spaces and underscores to hyphens\n * - remove special characters except hyphens and dots\n * - collapse multiple hyphens\n * - preserve file extension\n */\nexport function slugifyFilename(filename: string): string {\n const ext = path.extname(filename).toLowerCase()\n const baseName = path.basename(filename, path.extname(filename))\n \n const slugged = baseName\n .toLowerCase()\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '') // Remove diacritics\n .replace(/[_\\s]+/g, '-') // Replace spaces and underscores with hyphens\n .replace(/[^a-z0-9-]/g, '') // Remove non-alphanumeric except hyphens\n .replace(/-+/g, '-') // Collapse multiple hyphens\n .replace(/^-|-$/g, '') // Trim leading/trailing hyphens\n \n // If the slug is empty after processing, use a fallback\n const finalSlug = slugged || 'file'\n \n return finalSlug + ext\n}\n\n/**\n * Slugify a folder name (no extension handling)\n */\nexport function slugifyFolderName(name: string): string {\n const slugged = name\n .toLowerCase()\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '') // Remove diacritics\n .replace(/[_\\s]+/g, '-') // Replace spaces and underscores with hyphens\n .replace(/[^a-z0-9-]/g, '') // Remove non-alphanumeric except hyphens\n .replace(/-+/g, '-') // Collapse multiple hyphens\n .replace(/^-|-$/g, '') // Trim leading/trailing hyphens\n \n return slugged || 'folder'\n}\n\nexport function isImageFile(filename: string): boolean {\n const ext = path.extname(filename).toLowerCase()\n return ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.ico', '.bmp', '.tiff', '.tif'].includes(ext)\n}\n\nexport function isMediaFile(filename: string): boolean {\n const ext = path.extname(filename).toLowerCase()\n // Images\n if (['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.ico', '.bmp', '.tiff', '.tif'].includes(ext)) return true\n // Videos\n if (['.mp4', '.webm', '.mov', '.avi', '.mkv', '.m4v'].includes(ext)) return true\n // Audio\n if (['.mp3', '.wav', '.ogg', '.m4a', '.flac', '.aac'].includes(ext)) return true\n // Documents\n if (['.pdf', '.json'].includes(ext)) return true\n return false\n}\n\nexport function getContentType(filePath: string): string {\n const ext = path.extname(filePath).toLowerCase()\n switch (ext) {\n case '.jpg':\n case '.jpeg':\n return 'image/jpeg'\n case '.png':\n return 'image/png'\n case '.gif':\n return 'image/gif'\n case '.webp':\n return 'image/webp'\n case '.svg':\n return 'image/svg+xml'\n default:\n return 'application/octet-stream'\n }\n}\n\nexport async function getFolderStats(folderPath: string): Promise<{ fileCount: number; totalSize: number }> {\n let fileCount = 0\n let totalSize = 0\n\n async function scanFolder(dir: string): Promise<void> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true })\n for (const entry of entries) {\n if (entry.name.startsWith('.')) continue\n const fullPath = path.join(dir, entry.name)\n if (entry.isDirectory()) {\n await scanFolder(fullPath)\n } else if (isMediaFile(entry.name)) {\n fileCount++\n const stats = await fs.stat(fullPath)\n totalSize += stats.size\n }\n }\n } catch { /* ignore errors */ }\n }\n\n await scanFolder(folderPath)\n return { fileCount, totalSize }\n}\n","import { promises as fs } from \"fs\";\nimport path from \"path\";\nimport sharp from \"sharp\";\nimport type { MetaEntry, Dimensions } from \"../../types\";\nimport { getPublicPath } from \"../../config\";\n\nexport const FULL_MAX_WIDTH = 2560;\n\nexport const DEFAULT_SIZES: Record<\n string,\n { width: number; suffix: string; key: \"sm\" | \"md\" | \"lg\" }\n> = {\n small: { width: 300, suffix: \"-sm\", key: \"sm\" },\n medium: { width: 700, suffix: \"-md\", key: \"md\" },\n large: { width: 1400, suffix: \"-lg\", key: \"lg\" },\n};\n\nexport async function processImage(\n buffer: Buffer,\n imageKey: string\n): Promise<MetaEntry> {\n // Apply EXIF rotation first to get correct dimensions\n // Many cameras store images rotated with EXIF metadata to display correctly\n const rotatedBuffer = await sharp(buffer).rotate().toBuffer();\n const metadata = await sharp(rotatedBuffer).metadata();\n const originalWidth = metadata.width || 0;\n const originalHeight = metadata.height || 0;\n const ratio = originalHeight / originalWidth;\n\n // Remove leading slash for path operations\n const keyWithoutSlash = imageKey.startsWith(\"/\")\n ? imageKey.slice(1)\n : imageKey;\n const baseName = path.basename(\n keyWithoutSlash,\n path.extname(keyWithoutSlash)\n );\n const ext = path.extname(keyWithoutSlash).toLowerCase();\n const imageDir = path.dirname(keyWithoutSlash);\n\n const imagesPath = getPublicPath(\"images\", imageDir === \".\" ? \"\" : imageDir);\n await fs.mkdir(imagesPath, { recursive: true });\n\n const isPng = ext === \".png\";\n const outputExt = isPng ? \".png\" : \".jpg\";\n\n // Build the result entry\n const entry: MetaEntry = {\n o: { w: originalWidth, h: originalHeight },\n };\n\n // Generate full size (capped at FULL_MAX_WIDTH)\n const fullFileName =\n imageDir === \".\"\n ? `${baseName}${outputExt}`\n : `${imageDir}/${baseName}${outputExt}`;\n const fullPath = getPublicPath(\"images\", fullFileName);\n\n let fullWidth = originalWidth;\n let fullHeight = originalHeight;\n\n if (originalWidth > FULL_MAX_WIDTH) {\n fullWidth = FULL_MAX_WIDTH;\n fullHeight = Math.round(FULL_MAX_WIDTH * ratio);\n if (isPng) {\n await sharp(rotatedBuffer)\n .resize(fullWidth, fullHeight)\n .png({ quality: 85 })\n .toFile(fullPath);\n } else {\n await sharp(rotatedBuffer)\n .resize(fullWidth, fullHeight)\n .jpeg({ quality: 85 })\n .toFile(fullPath);\n }\n } else {\n if (isPng) {\n await sharp(rotatedBuffer).png({ quality: 85 }).toFile(fullPath);\n } else {\n await sharp(rotatedBuffer).jpeg({ quality: 85 }).toFile(fullPath);\n }\n }\n entry.f = { w: fullWidth, h: fullHeight };\n\n // Generate thumbnail sizes\n for (const [, sizeConfig] of Object.entries(DEFAULT_SIZES)) {\n const { width: maxWidth, suffix, key } = sizeConfig;\n if (originalWidth <= maxWidth) {\n continue; // Skip if original is smaller than this size\n }\n\n const newHeight = Math.round(maxWidth * ratio);\n const sizeFileName = `${baseName}${suffix}${outputExt}`;\n const sizeFilePath =\n imageDir === \".\" ? sizeFileName : `${imageDir}/${sizeFileName}`;\n const sizePath = getPublicPath(\"images\", sizeFilePath);\n\n if (isPng) {\n await sharp(rotatedBuffer)\n .resize(maxWidth, newHeight)\n .png({ quality: 80 })\n .toFile(sizePath);\n } else {\n await sharp(rotatedBuffer)\n .resize(maxWidth, newHeight)\n .jpeg({ quality: 80 })\n .toFile(sizePath);\n }\n\n entry[key] = { w: maxWidth, h: newHeight };\n }\n\n return entry;\n}\n","import { promises as fs } from 'fs'\nimport { S3Client, GetObjectCommand, PutObjectCommand, DeleteObjectCommand, CopyObjectCommand } from '@aws-sdk/client-s3'\nimport { getAllThumbnailPaths } from '../../types'\nimport { getContentType } from './files'\nimport { getPublicPath } from '../../config'\n\nfunction getR2Client() {\n const accountId = process.env.CLOUDFLARE_R2_ACCOUNT_ID\n const accessKeyId = process.env.CLOUDFLARE_R2_ACCESS_KEY_ID\n const secretAccessKey = process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY\n\n if (!accountId || !accessKeyId || !secretAccessKey) {\n throw new Error('R2 not configured')\n }\n\n return new S3Client({\n region: 'auto',\n endpoint: `https://${accountId}.r2.cloudflarestorage.com`,\n credentials: { accessKeyId, secretAccessKey },\n })\n}\n\nexport async function downloadFromCdn(originalPath: string): Promise<Buffer> {\n const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME\n if (!bucketName) throw new Error('R2 bucket not configured')\n\n const r2 = getR2Client()\n const maxRetries = 3\n let lastError: Error | undefined\n\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n const response = await r2.send(\n new GetObjectCommand({\n Bucket: bucketName,\n Key: originalPath.replace(/^\\//, ''),\n })\n )\n\n const stream = response.Body as NodeJS.ReadableStream\n const chunks: Buffer[] = []\n for await (const chunk of stream) {\n chunks.push(Buffer.from(chunk))\n }\n return Buffer.concat(chunks)\n } catch (error) {\n lastError = error as Error\n // Wait before retry (exponential backoff: 500ms, 1s)\n if (attempt < maxRetries - 1) {\n await new Promise(resolve => setTimeout(resolve, 500 * (attempt + 1)))\n }\n }\n }\n\n throw lastError || new Error(`Failed to download ${originalPath} after ${maxRetries} attempts`)\n}\n\nexport async function uploadToCdn(imageKey: string): Promise<void> {\n const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME\n if (!bucketName) throw new Error('R2 bucket not configured')\n\n const r2 = getR2Client()\n\n // Upload all thumbnail sizes derived from imageKey\n for (const thumbPath of getAllThumbnailPaths(imageKey)) {\n const localPath = getPublicPath(thumbPath)\n try {\n const fileBuffer = await fs.readFile(localPath)\n await r2.send(\n new PutObjectCommand({\n Bucket: bucketName,\n Key: thumbPath.replace(/^\\//, ''),\n Body: fileBuffer,\n ContentType: getContentType(thumbPath),\n })\n )\n } catch {\n // File might not exist (e.g., if image is smaller than thumbnail size)\n }\n }\n}\n\nexport async function deleteLocalThumbnails(imageKey: string): Promise<void> {\n for (const thumbPath of getAllThumbnailPaths(imageKey)) {\n const localPath = getPublicPath(thumbPath)\n try {\n await fs.unlink(localPath)\n } catch {\n // File might not exist\n }\n }\n}\n\n/**\n * Download image from a remote URL (not R2)\n */\nexport async function downloadFromRemoteUrl(url: string): Promise<Buffer> {\n const maxRetries = 3\n let lastError: Error | undefined\n\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n const response = await fetch(url)\n if (!response.ok) {\n throw new Error(`Failed to download from ${url}: ${response.status}`)\n }\n const arrayBuffer = await response.arrayBuffer()\n return Buffer.from(arrayBuffer)\n } catch (error) {\n lastError = error as Error\n // Wait before retry (exponential backoff: 500ms, 1s)\n if (attempt < maxRetries - 1) {\n await new Promise(resolve => setTimeout(resolve, 500 * (attempt + 1)))\n }\n }\n }\n\n throw lastError || new Error(`Failed to download from ${url} after ${maxRetries} attempts`)\n}\n\n/**\n * Upload original image to R2 CDN\n */\nexport async function uploadOriginalToCdn(imageKey: string): Promise<void> {\n const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME\n if (!bucketName) throw new Error('R2 bucket not configured')\n\n const r2 = getR2Client()\n const localPath = getPublicPath(imageKey)\n const fileBuffer = await fs.readFile(localPath)\n \n await r2.send(\n new PutObjectCommand({\n Bucket: bucketName,\n Key: imageKey.replace(/^\\//, ''),\n Body: fileBuffer,\n ContentType: getContentType(imageKey),\n })\n )\n}\n\n/**\n * Delete original and thumbnails from R2 CDN\n */\nexport async function deleteFromCdn(imageKey: string, hasThumbnails: boolean): Promise<void> {\n const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME\n if (!bucketName) throw new Error('R2 bucket not configured')\n\n const r2 = getR2Client()\n\n // Delete original\n try {\n await r2.send(\n new DeleteObjectCommand({\n Bucket: bucketName,\n Key: imageKey.replace(/^\\//, ''),\n })\n )\n } catch {\n // May not exist\n }\n\n // Delete thumbnails if they exist\n if (hasThumbnails) {\n for (const thumbPath of getAllThumbnailPaths(imageKey)) {\n try {\n await r2.send(\n new DeleteObjectCommand({\n Bucket: bucketName,\n Key: thumbPath.replace(/^\\//, ''),\n })\n )\n } catch {\n // May not exist\n }\n }\n }\n}\n\n/**\n * Delete only thumbnails from R2 CDN (keeps original)\n */\nexport async function deleteThumbnailsFromCdn(imageKey: string): Promise<void> {\n const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME\n if (!bucketName) throw new Error('R2 bucket not configured')\n\n const r2 = getR2Client()\n\n for (const thumbPath of getAllThumbnailPaths(imageKey)) {\n try {\n await r2.send(\n new DeleteObjectCommand({\n Bucket: bucketName,\n Key: thumbPath.replace(/^\\//, ''),\n })\n )\n } catch {\n // May not exist\n }\n }\n}\n\n/**\n * Delete only original from R2 CDN (keeps thumbnails)\n * Used for cache busting before re-upload\n */\nexport async function deleteOriginalFromCdn(imageKey: string): Promise<void> {\n const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME\n if (!bucketName) throw new Error('R2 bucket not configured')\n\n const r2 = getR2Client()\n\n try {\n await r2.send(\n new DeleteObjectCommand({\n Bucket: bucketName,\n Key: imageKey.replace(/^\\//, ''),\n })\n )\n } catch {\n // May not exist\n }\n}\n\n/**\n * Copy a file within R2 CDN (server-side, no download/upload)\n * Used for rename/move operations - much faster than download+upload\n */\nexport async function copyInCdn(oldKey: string, newKey: string): Promise<void> {\n const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME\n if (!bucketName) throw new Error('R2 bucket not configured')\n\n const r2 = getR2Client()\n const oldKeyClean = oldKey.replace(/^\\//, '')\n const newKeyClean = newKey.replace(/^\\//, '')\n\n await r2.send(\n new CopyObjectCommand({\n Bucket: bucketName,\n CopySource: `${bucketName}/${oldKeyClean}`,\n Key: newKeyClean,\n })\n )\n}\n\n/**\n * Move a file within R2 CDN (copy + delete, server-side)\n * Handles original and thumbnails\n */\nexport async function moveInCdn(oldKey: string, newKey: string, hasThumbnails: boolean): Promise<void> {\n // Copy original\n await copyInCdn(oldKey, newKey)\n \n // Copy thumbnails if they exist\n if (hasThumbnails) {\n const oldThumbPaths = getAllThumbnailPaths(oldKey)\n const newThumbPaths = getAllThumbnailPaths(newKey)\n \n for (let i = 0; i < oldThumbPaths.length; i++) {\n try {\n await copyInCdn(oldThumbPaths[i], newThumbPaths[i])\n } catch {\n // Thumbnail might not exist\n }\n }\n }\n \n // Delete old files\n await deleteFromCdn(oldKey, hasThumbnails)\n}\n","/**\n * Dimensions object {w, h}\n */\nexport interface Dimensions {\n w: number;\n h: number;\n}\n\n/**\n * Meta entry - works for images and non-images\n * o: original dimensions, c: CDN index\n * sm/md/lg/f: thumbnail dimensions (presence implies processed)\n */\nexport interface MetaEntry {\n o?: Dimensions; // original dimensions {w, h}\n sm?: Dimensions; // small thumbnail (300px width)\n md?: Dimensions; // medium thumbnail (700px width)\n lg?: Dimensions; // large thumbnail (1400px width)\n f?: Dimensions; // full size (capped at 2560px width)\n c?: number; // CDN index - index into _cdns array\n u?: 1; // update pending - local file overrides cloud file\n b?: string; // blur hash or base64 placeholder\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\n * Example: { \"/portfolio/photo.jpg\": { o: {w:2400,h:1600}, sm: {w:300,h:200}, ... } }\n */\nexport type LeanMeta = Record<string, MetaEntry>;\n\n// 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 isCloud?: boolean; // true if file exists in cloud (CDN pushed or remote)\n isProtected?: boolean; // true for images folder and its contents (cannot select/modify)\n // Folder-specific properties\n fileCount?: number;\n totalSize?: number;\n cloudCount?: number; // Number of R2 cloud files in folder\n remoteCount?: number; // Number of remote (imported URL) files in folder\n localCount?: number; // Number of local files in folder\n // For showing thumbnails - path to -sm version if exists\n thumbnail?: string;\n // Whether a processed thumbnail exists\n hasThumbnail?: boolean;\n // Which thumbnail sizes exist\n hasSm?: boolean;\n hasMd?: boolean;\n hasLg?: boolean;\n hasFull?: boolean;\n // Update pending - local file overrides cloud file\n hasUpdate?: boolean;\n // Number of files with pending updates in folder\n updateCount?: number;\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(\n originalPath: string,\n size: \"sm\" | \"md\" | \"lg\" | \"full\"\n): 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\n/**\n * Check if an image entry is processed (has any thumbnail dimensions)\n */\nexport function isProcessed(entry: MetaEntry | undefined): boolean {\n if (!entry) return false;\n return !!(entry.f || entry.lg || entry.md || entry.sm);\n}\n","/**\n * Utility functions to replace NextResponse with standard Response\n * This allows handlers to work without Next.js dependency\n */\n\n/**\n * Create a JSON response (mimics NextResponse.json)\n */\nexport function jsonResponse<T>(\n data: T,\n init?: { status?: number; headers?: Record<string, string> }\n): Response {\n const headers = new Headers({\n 'Content-Type': 'application/json',\n ...init?.headers,\n })\n\n return new Response(JSON.stringify(data), {\n status: init?.status ?? 200,\n headers,\n })\n}\n\n/**\n * Create a streaming response for Server-Sent Events\n */\nexport function streamResponse(\n stream: ReadableStream,\n init?: { headers?: Record<string, string> }\n): Response {\n const headers = new Headers({\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n ...init?.headers,\n })\n\n return new Response(stream, {\n status: 200,\n headers,\n })\n}\n\n/**\n * Create an SSE encoder for streaming responses\n */\nexport function createSSEStream() {\n const encoder = new TextEncoder()\n let controller: ReadableStreamDefaultController<Uint8Array> | null = null\n\n const stream = new ReadableStream<Uint8Array>({\n start(c) {\n controller = c\n },\n })\n\n return {\n stream,\n send(event: string, data: unknown) {\n if (controller) {\n const message = `event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`\n controller.enqueue(encoder.encode(message))\n }\n },\n close() {\n if (controller) {\n controller.close()\n }\n },\n }\n}\n","import { promises as fs } from \"fs\";\nimport path from \"path\";\nimport sharp from \"sharp\";\nimport type { MetaEntry } from \"../types\";\nimport { getAllThumbnailPaths, isProcessed } from \"../types\";\nimport {\n loadMeta,\n saveMeta,\n isImageFile,\n isMediaFile,\n getCdnUrls,\n downloadFromCdn,\n downloadFromRemoteUrl,\n uploadOriginalToCdn,\n uploadToCdn,\n deleteFromCdn,\n deleteLocalThumbnails,\n processImage,\n slugifyFilename,\n slugifyFolderName,\n moveInCdn,\n} from \"./utils\";\nimport { getPublicPath, getWorkspacePath } from \"../config\";\nimport {\n jsonResponse,\n streamResponse,\n createSSEStream,\n} from \"./utils/response\";\nimport { deleteEmptyFolders } from \"./utils/folders\";\nimport { isOperationCancelled, clearCancelledOperation } from \"./images\";\n\nexport async function handleUpload(request: Request) {\n try {\n const formData = await request.formData();\n const file = formData.get(\"file\") as File | null;\n const targetPath = (formData.get(\"path\") as string) || \"public\";\n\n if (!file) {\n return jsonResponse({ error: \"No file provided\" }, { status: 400 });\n }\n\n const bytes = await file.arrayBuffer();\n const buffer = Buffer.from(bytes);\n\n // Slugify filename to be URL-safe (lowercase, no spaces, etc.)\n const fileName = slugifyFilename(file.name);\n const ext = path.extname(fileName).toLowerCase();\n\n const isImage = isImageFile(fileName);\n const isMedia = isMediaFile(fileName);\n\n const meta = await loadMeta();\n\n let relativeDir = \"\";\n if (targetPath === \"public\") {\n relativeDir = \"\";\n } else if (targetPath.startsWith(\"public/\")) {\n relativeDir = targetPath.replace(\"public/\", \"\");\n }\n\n if (relativeDir === \"images\" || relativeDir.startsWith(\"images/\")) {\n return jsonResponse(\n {\n error:\n \"Cannot upload to images/ folder. Upload to public/ instead - thumbnails are generated automatically.\",\n },\n { status: 400 }\n );\n }\n\n // Build the meta key\n let imageKey =\n \"/\" + (relativeDir ? `${relativeDir}/${fileName}` : fileName);\n\n // Check for collision - rename if needed\n if (meta[imageKey]) {\n const baseName = path.basename(fileName, ext);\n let counter = 1;\n let newFileName = `${baseName}-${counter}${ext}`;\n let newKey =\n \"/\" + (relativeDir ? `${relativeDir}/${newFileName}` : newFileName);\n\n while (meta[newKey]) {\n counter++;\n newFileName = `${baseName}-${counter}${ext}`;\n newKey =\n \"/\" + (relativeDir ? `${relativeDir}/${newFileName}` : newFileName);\n }\n\n imageKey = newKey;\n }\n\n // Extract actual filename from key\n const actualFileName = path.basename(imageKey);\n\n const uploadDir = getPublicPath(relativeDir);\n await fs.mkdir(uploadDir, { recursive: true });\n await fs.writeFile(path.join(uploadDir, actualFileName), buffer);\n\n if (!isMedia) {\n return jsonResponse({\n success: true,\n message: \"File uploaded (not a media file)\",\n path: `public/${relativeDir ? relativeDir + \"/\" : \"\"}${actualFileName}`,\n });\n }\n\n // Add to meta\n if (isImage && ext !== \".svg\") {\n // Read dimensions for images (apply EXIF rotation to get correct dimensions)\n try {\n const rotatedBuffer = await sharp(buffer).rotate().toBuffer();\n const metadata = await sharp(rotatedBuffer).metadata();\n meta[imageKey] = {\n o: { w: metadata.width || 0, h: metadata.height || 0 },\n };\n } catch {\n meta[imageKey] = { o: { w: 0, h: 0 } };\n }\n } else {\n // Non-image media or SVG\n meta[imageKey] = {};\n }\n\n await saveMeta(meta);\n\n return jsonResponse({\n success: true,\n imageKey,\n message: 'File uploaded. Run \"Process Images\" to generate thumbnails.',\n });\n } catch (error) {\n console.error(\"Failed to upload:\", error);\n const message = error instanceof Error ? error.message : \"Unknown error\";\n return jsonResponse(\n { error: `Failed to upload file: ${message}` },\n { status: 500 }\n );\n }\n}\n\nexport async function handleDelete(request: Request) {\n try {\n const { paths } = (await request.json()) as { paths: string[] };\n\n if (!paths || !Array.isArray(paths) || paths.length === 0) {\n return jsonResponse({ error: \"No paths provided\" }, { status: 400 });\n }\n\n const meta = await loadMeta();\n const deleted: string[] = [];\n const errors: string[] = [];\n const sourceFolders = new Set<string>();\n\n for (const itemPath of paths) {\n try {\n if (!itemPath.startsWith(\"public/\")) {\n errors.push(`Invalid path: ${itemPath}`);\n continue;\n }\n\n const absolutePath = getWorkspacePath(itemPath);\n const imageKey = \"/\" + itemPath.replace(/^public\\//, \"\");\n\n // Track source folder for cleanup\n sourceFolders.add(path.dirname(absolutePath));\n\n // Check if this is in meta (could be synced with no local file)\n const entry = meta[imageKey] as MetaEntry | undefined;\n const isPushedToCloud = entry?.c !== undefined;\n const hasThumbnails = entry ? isProcessed(entry) : false;\n\n // Try to delete local file/folder\n try {\n const stats = await fs.stat(absolutePath);\n\n if (stats.isDirectory()) {\n await fs.rm(absolutePath, { recursive: true });\n\n // Remove all meta entries under this folder\n const prefix = imageKey + \"/\";\n for (const key of Object.keys(meta)) {\n if (key.startsWith(prefix) || key === imageKey) {\n const keyEntry = meta[key] as MetaEntry | undefined;\n const keyHasThumbnails = keyEntry\n ? isProcessed(keyEntry)\n : false;\n\n // Delete from CDN if pushed\n if (keyEntry?.c !== undefined) {\n try {\n await deleteFromCdn(key, keyHasThumbnails);\n } catch {\n /* ignore CDN delete errors */\n }\n } else {\n // Delete local thumbnails if not synced\n for (const thumbPath of getAllThumbnailPaths(key)) {\n const absoluteThumbPath = getPublicPath(thumbPath);\n try {\n await fs.unlink(absoluteThumbPath);\n } catch {\n /* ignore */\n }\n }\n }\n delete meta[key];\n }\n }\n } else {\n await fs.unlink(absolutePath);\n\n const isInImagesFolder = itemPath.startsWith(\"public/images/\");\n\n if (!isInImagesFolder && entry) {\n // Delete from CDN if pushed\n if (isPushedToCloud) {\n try {\n await deleteFromCdn(imageKey, hasThumbnails);\n } catch {\n /* ignore CDN delete errors */\n }\n } else {\n // Delete local thumbnails if not synced\n for (const thumbPath of getAllThumbnailPaths(imageKey)) {\n const absoluteThumbPath = getPublicPath(thumbPath);\n try {\n await fs.unlink(absoluteThumbPath);\n } catch {\n /* ignore */\n }\n }\n }\n delete meta[imageKey];\n }\n }\n } catch {\n // File doesn't exist locally - might be synced\n if (entry) {\n // Delete from CDN if pushed\n if (isPushedToCloud) {\n try {\n await deleteFromCdn(imageKey, hasThumbnails);\n } catch {\n /* ignore CDN delete errors */\n }\n }\n delete meta[imageKey];\n } else {\n // Check if it's a folder prefix in meta\n const prefix = imageKey + \"/\";\n let foundAny = false;\n for (const key of Object.keys(meta)) {\n if (key.startsWith(prefix)) {\n const keyEntry = meta[key] as MetaEntry | undefined;\n const keyHasThumbnails = keyEntry\n ? isProcessed(keyEntry)\n : false;\n // Delete from CDN if pushed\n if (keyEntry?.c !== undefined) {\n try {\n await deleteFromCdn(key, keyHasThumbnails);\n } catch {\n /* ignore CDN delete errors */\n }\n }\n delete meta[key];\n foundAny = true;\n }\n }\n if (!foundAny) {\n errors.push(`Not found: ${itemPath}`);\n continue;\n }\n }\n }\n\n deleted.push(itemPath);\n } catch (error) {\n console.error(`Failed to delete ${itemPath}:`, error);\n errors.push(itemPath);\n }\n }\n\n await saveMeta(meta);\n\n // Clean up empty source folders\n for (const folder of sourceFolders) {\n await deleteEmptyFolders(folder);\n }\n\n return jsonResponse({\n success: true,\n deleted,\n errors: errors.length > 0 ? errors : undefined,\n });\n } catch (error) {\n console.error(\"Failed to delete:\", error);\n return jsonResponse({ error: \"Failed to delete files\" }, { status: 500 });\n }\n}\n\n/**\n * Delete files/folders with streaming progress\n */\nexport async function handleDeleteStream(request: Request) {\n const encoder = new TextEncoder();\n\n const stream = new ReadableStream({\n async start(controller) {\n const sendEvent = (data: object) => {\n try {\n controller.enqueue(\n encoder.encode(`data: ${JSON.stringify(data)}\\n\\n`)\n );\n } catch {\n // Controller may be closed\n }\n };\n\n try {\n const { paths, operationId } = (await request.json()) as {\n paths: string[];\n operationId?: string;\n };\n\n if (!paths || !Array.isArray(paths) || paths.length === 0) {\n sendEvent({ type: \"error\", message: \"No paths provided\" });\n controller.close();\n return;\n }\n\n // Helper to check if operation was cancelled\n const isCancelled = () =>\n operationId ? isOperationCancelled(operationId) : false;\n\n const meta = await loadMeta();\n const deleted: string[] = [];\n const errors: string[] = [];\n const sourceFolders = new Set<string>();\n const total = paths.length;\n\n sendEvent({ type: \"start\", total });\n\n for (let i = 0; i < paths.length; i++) {\n // Check for cancellation before each item\n if (isCancelled()) {\n await saveMeta(meta);\n // Clean up empty folders\n for (const folder of sourceFolders) {\n await deleteEmptyFolders(folder);\n }\n if (operationId) clearCancelledOperation(operationId);\n sendEvent({\n type: \"complete\",\n deleted: deleted.length,\n errors: errors.length,\n message: `Stopped. Deleted ${deleted.length} item${\n deleted.length !== 1 ? \"s\" : \"\"\n }.`,\n cancelled: true,\n });\n controller.close();\n return;\n }\n\n const itemPath = paths[i];\n\n try {\n if (!itemPath.startsWith(\"public/\")) {\n errors.push(`Invalid path: ${itemPath}`);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n deleted: deleted.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: path.basename(itemPath),\n });\n continue;\n }\n\n const absolutePath = getWorkspacePath(itemPath);\n const imageKey = \"/\" + itemPath.replace(/^public\\//, \"\");\n\n // Track source folder for cleanup\n sourceFolders.add(path.dirname(absolutePath));\n\n // Check if this is in meta (could be synced with no local file)\n const entry = meta[imageKey] as MetaEntry | undefined;\n const isPushedToCloud = entry?.c !== undefined;\n const hasThumbnails = entry ? isProcessed(entry) : false;\n\n // Try to delete local file/folder\n try {\n const stats = await fs.stat(absolutePath);\n\n if (stats.isDirectory()) {\n await fs.rm(absolutePath, { recursive: true });\n\n // Remove all meta entries under this folder\n const prefix = imageKey + \"/\";\n for (const key of Object.keys(meta)) {\n if (key.startsWith(prefix) || key === imageKey) {\n const keyEntry = meta[key] as MetaEntry | undefined;\n const keyHasThumbnails = keyEntry\n ? isProcessed(keyEntry)\n : false;\n\n // Delete from CDN if pushed\n if (keyEntry?.c !== undefined) {\n try {\n await deleteFromCdn(key, keyHasThumbnails);\n } catch {\n /* ignore CDN delete errors */\n }\n } else {\n // Delete local thumbnails if not synced\n for (const thumbPath of getAllThumbnailPaths(key)) {\n const absoluteThumbPath = getPublicPath(thumbPath);\n try {\n await fs.unlink(absoluteThumbPath);\n } catch {\n /* ignore */\n }\n }\n }\n delete meta[key];\n }\n }\n } else {\n await fs.unlink(absolutePath);\n\n const isInImagesFolder = itemPath.startsWith(\"public/images/\");\n\n if (!isInImagesFolder && entry) {\n // Delete from CDN if pushed\n if (isPushedToCloud) {\n try {\n await deleteFromCdn(imageKey, hasThumbnails);\n } catch {\n /* ignore CDN delete errors */\n }\n } else {\n // Delete local thumbnails if not synced\n for (const thumbPath of getAllThumbnailPaths(imageKey)) {\n const absoluteThumbPath = getPublicPath(thumbPath);\n try {\n await fs.unlink(absoluteThumbPath);\n } catch {\n /* ignore */\n }\n }\n }\n delete meta[imageKey];\n }\n }\n } catch {\n // File doesn't exist locally - might be synced\n if (entry) {\n // Delete from CDN if pushed\n if (isPushedToCloud) {\n try {\n await deleteFromCdn(imageKey, hasThumbnails);\n } catch {\n /* ignore CDN delete errors */\n }\n }\n delete meta[imageKey];\n } else {\n // Check if it's a folder prefix in meta\n const prefix = imageKey + \"/\";\n let foundAny = false;\n for (const key of Object.keys(meta)) {\n if (key.startsWith(prefix)) {\n const keyEntry = meta[key] as MetaEntry | undefined;\n const keyHasThumbnails = keyEntry\n ? isProcessed(keyEntry)\n : false;\n // Delete from CDN if pushed\n if (keyEntry?.c !== undefined) {\n try {\n await deleteFromCdn(key, keyHasThumbnails);\n } catch {\n /* ignore CDN delete errors */\n }\n }\n delete meta[key];\n foundAny = true;\n }\n }\n if (!foundAny) {\n errors.push(`Not found: ${itemPath}`);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n deleted: deleted.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: path.basename(itemPath),\n });\n continue;\n }\n }\n }\n\n // Save meta incrementally\n await saveMeta(meta);\n deleted.push(itemPath);\n } catch (error) {\n console.error(`Failed to delete ${itemPath}:`, error);\n errors.push(itemPath);\n }\n\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n deleted: deleted.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: path.basename(itemPath),\n });\n }\n\n // Clean up empty source folders\n for (const folder of sourceFolders) {\n await deleteEmptyFolders(folder);\n }\n\n if (operationId) clearCancelledOperation(operationId);\n sendEvent({\n type: \"complete\",\n deleted: deleted.length,\n errors: errors.length,\n errorMessages: errors.length > 0 ? errors : undefined,\n });\n } catch (error) {\n console.error(\"Failed to delete:\", error);\n sendEvent({ type: \"error\", message: \"Failed to delete files\" });\n } finally {\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n}\n\nexport async function handleCreateFolder(request: Request) {\n try {\n const { parentPath, name } = await request.json();\n\n if (!name || typeof name !== \"string\") {\n return jsonResponse(\n { error: \"Folder name is required\" },\n { status: 400 }\n );\n }\n\n // Slugify folder name to be URL-safe (lowercase, no spaces, etc.)\n const sanitizedName = slugifyFolderName(name);\n if (!sanitizedName) {\n return jsonResponse({ error: \"Invalid folder name\" }, { status: 400 });\n }\n\n const safePath = (parentPath || \"public\").replace(/\\.\\./g, \"\");\n const folderPath = getWorkspacePath(safePath, sanitizedName);\n\n if (!folderPath.startsWith(getPublicPath())) {\n return jsonResponse({ error: \"Invalid path\" }, { status: 400 });\n }\n\n try {\n await fs.access(folderPath);\n return jsonResponse(\n { error: \"A folder with this name already exists\" },\n { status: 400 }\n );\n } catch {\n // Good - folder doesn't exist\n }\n\n await fs.mkdir(folderPath, { recursive: true });\n\n return jsonResponse({\n success: true,\n path: path.join(safePath, sanitizedName),\n });\n } catch (error) {\n console.error(\"Failed to create folder:\", error);\n return jsonResponse({ error: \"Failed to create folder\" }, { status: 500 });\n }\n}\n\nexport async function handleRename(request: Request) {\n const publicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/$/, \"\");\n\n try {\n const { oldPath, newName } = await request.json();\n\n if (!oldPath || !newName) {\n return jsonResponse(\n { error: \"Path and new name are required\" },\n { status: 400 }\n );\n }\n\n const safePath = oldPath.replace(/\\.\\./g, \"\");\n const absoluteOldPath = getWorkspacePath(safePath);\n\n if (!absoluteOldPath.startsWith(getPublicPath())) {\n return jsonResponse({ error: \"Invalid path\" }, { status: 400 });\n }\n\n const oldRelativePath = safePath.replace(/^public\\//, \"\");\n const oldKey = \"/\" + oldRelativePath;\n const isImage = isImageFile(path.basename(oldPath));\n\n // Load meta to check if this is a cloud file\n const meta = await loadMeta();\n const cdnUrls = getCdnUrls(meta);\n const entry = meta[oldKey] as MetaEntry | undefined;\n const isInCloud = entry?.c !== undefined;\n const fileCdnUrl =\n isInCloud && entry.c !== undefined ? cdnUrls[entry.c] : undefined;\n const isInOurR2 = isInCloud && fileCdnUrl === publicUrl;\n const hasThumbnails = entry ? isProcessed(entry) : false;\n\n // Check if local file exists\n let hasLocalFile = false;\n let isFile = true;\n try {\n const stats = await fs.stat(absoluteOldPath);\n hasLocalFile = true;\n isFile = stats.isFile();\n } catch {\n // No local file - might be cloud-only\n if (!isInCloud) {\n return jsonResponse(\n { error: \"File or folder not found\" },\n { status: 404 }\n );\n }\n }\n\n // Slugify name based on whether it's a file or folder\n const sanitizedName = isFile\n ? slugifyFilename(newName)\n : slugifyFolderName(newName);\n if (!sanitizedName) {\n return jsonResponse({ error: \"Invalid name\" }, { status: 400 });\n }\n\n const parentDir = path.dirname(absoluteOldPath);\n const absoluteNewPath = path.join(parentDir, sanitizedName);\n const newRelativePath = path.join(\n path.dirname(oldRelativePath),\n sanitizedName\n );\n const newKey = \"/\" + newRelativePath;\n\n // Check if new name already exists in meta\n if (meta[newKey]) {\n return jsonResponse(\n { error: \"An item with this name already exists\" },\n { status: 400 }\n );\n }\n\n // Check if new local path already exists\n try {\n await fs.access(absoluteNewPath);\n return jsonResponse(\n { error: \"An item with this name already exists\" },\n { status: 400 }\n );\n } catch {\n // Good - new path doesn't exist\n }\n\n // Handle cloud-only file: use server-side copy in R2\n if (isInOurR2 && !hasLocalFile) {\n // Server-side rename in R2 (copy + delete, no download needed)\n await moveInCdn(oldKey, newKey, hasThumbnails);\n\n // Update meta\n delete meta[oldKey];\n meta[newKey] = entry;\n await saveMeta(meta);\n\n const newPath = path.join(path.dirname(safePath), sanitizedName);\n return jsonResponse({ success: true, newPath });\n }\n\n // Handle local file rename\n if (hasLocalFile) {\n await fs.rename(absoluteOldPath, absoluteNewPath);\n }\n\n if (isImage && entry) {\n const oldThumbPaths = getAllThumbnailPaths(oldKey);\n const newThumbPaths = getAllThumbnailPaths(newKey);\n\n // Rename local thumbnails\n for (let i = 0; i < oldThumbPaths.length; i++) {\n const oldThumbPath = getPublicPath(oldThumbPaths[i]);\n const newThumbPath = getPublicPath(newThumbPaths[i]);\n\n await fs.mkdir(path.dirname(newThumbPath), { recursive: true });\n\n try {\n await fs.rename(oldThumbPath, newThumbPath);\n } catch {\n // Thumbnail might not exist\n }\n }\n\n // If file was in our R2, rename in cloud too (server-side)\n if (isInOurR2) {\n await moveInCdn(oldKey, newKey, hasThumbnails);\n\n // Clean up local files (they're now on CDN only)\n try {\n await fs.unlink(absoluteNewPath);\n } catch {\n /* ignore */\n }\n await deleteLocalThumbnails(newKey);\n }\n\n delete meta[oldKey];\n meta[newKey] = entry;\n await saveMeta(meta);\n }\n\n const newPath = path.join(path.dirname(safePath), sanitizedName);\n return jsonResponse({ success: true, newPath });\n } catch (error) {\n console.error(\"Failed to rename:\", error);\n return jsonResponse({ error: \"Failed to rename\" }, { status: 500 });\n }\n}\n\nexport async function handleRenameStream(request: Request) {\n const encoder = new TextEncoder();\n const publicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/$/, \"\");\n\n const stream = new ReadableStream({\n async start(controller) {\n const sendEvent = (data: object) => {\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\\n\\n`));\n };\n\n try {\n const { oldPath, newName, operationId } = await request.json();\n\n if (!oldPath || !newName) {\n sendEvent({\n type: \"error\",\n message: \"Path and new name are required\",\n });\n controller.close();\n return;\n }\n\n // Helper to check if operation was cancelled\n const isCancelled = () =>\n operationId ? isOperationCancelled(operationId) : false;\n\n const safePath = oldPath.replace(/\\.\\./g, \"\");\n const absoluteOldPath = getWorkspacePath(safePath);\n\n if (!absoluteOldPath.startsWith(getPublicPath())) {\n sendEvent({ type: \"error\", message: \"Invalid path\" });\n controller.close();\n return;\n }\n\n const oldRelativePath = safePath.replace(/^public\\//, \"\");\n const isImagePath = isImageFile(path.basename(oldPath));\n\n // Check if item exists and if it's a file or folder\n let hasLocalItem = false;\n let isFile = true;\n let isVirtualFolder = false;\n try {\n const stats = await fs.stat(absoluteOldPath);\n hasLocalItem = true;\n isFile = stats.isFile();\n } catch {\n // Check if it's a cloud-only file or virtual folder\n const meta = await loadMeta();\n const oldKey = \"/\" + oldRelativePath;\n const entry = meta[oldKey] as MetaEntry | undefined;\n\n if (entry) {\n // Cloud-only file\n isFile = true;\n } else {\n // Check if it's a virtual folder (folder that only exists in meta keys)\n const folderPrefix = oldKey + \"/\";\n const hasChildrenInMeta = Object.keys(meta).some((key) =>\n key.startsWith(folderPrefix)\n );\n\n if (hasChildrenInMeta) {\n // Virtual folder - exists only in meta\n isFile = false;\n isVirtualFolder = true;\n } else {\n sendEvent({ type: \"error\", message: \"File or folder not found\" });\n controller.close();\n return;\n }\n }\n }\n\n // Slugify name based on type\n const sanitizedName = isFile\n ? slugifyFilename(newName)\n : slugifyFolderName(newName);\n if (!sanitizedName) {\n sendEvent({ type: \"error\", message: \"Invalid name\" });\n controller.close();\n return;\n }\n\n const parentDir = path.dirname(absoluteOldPath);\n const absoluteNewPath = path.join(parentDir, sanitizedName);\n const newRelativePath = path.join(\n path.dirname(oldRelativePath),\n sanitizedName\n );\n const newPath = path.join(path.dirname(safePath), sanitizedName);\n\n // Check if destination exists\n const meta = await loadMeta();\n const cdnUrls = getCdnUrls(meta);\n\n // For files, check new key doesn't exist\n if (isFile) {\n const newKey = \"/\" + newRelativePath;\n if (meta[newKey]) {\n sendEvent({\n type: \"error\",\n message: \"An item with this name already exists\",\n });\n controller.close();\n return;\n }\n }\n\n // Only check local path if not a virtual folder\n if (!isVirtualFolder) {\n try {\n await fs.access(absoluteNewPath);\n sendEvent({\n type: \"error\",\n message: \"An item with this name already exists\",\n });\n controller.close();\n return;\n } catch {\n // Good - doesn't exist\n }\n }\n\n // For virtual folders, check if new prefix would conflict with existing meta keys\n if (isVirtualFolder) {\n const newPrefix = \"/\" + newRelativePath + \"/\";\n const hasConflict = Object.keys(meta).some((key) =>\n key.startsWith(newPrefix)\n );\n if (hasConflict) {\n sendEvent({\n type: \"error\",\n message: \"A folder with this name already exists\",\n });\n controller.close();\n return;\n }\n }\n\n // ========== FOLDER RENAME ==========\n if (!isFile) {\n // Collect all items in the folder that need meta updates\n const oldPrefix = \"/\" + oldRelativePath + \"/\";\n const newPrefix = \"/\" + newRelativePath + \"/\";\n\n // Find all meta entries under this folder\n const itemsToUpdate: Array<{\n oldKey: string;\n newKey: string;\n entry: MetaEntry;\n }> = [];\n for (const [key, entry] of Object.entries(meta)) {\n if (\n key.startsWith(oldPrefix) &&\n entry &&\n typeof entry === \"object\"\n ) {\n const newKey = key.replace(oldPrefix, newPrefix);\n itemsToUpdate.push({\n oldKey: key,\n newKey,\n entry: entry as MetaEntry,\n });\n }\n }\n\n const total = itemsToUpdate.length + 1; // +1 for the folder rename itself\n sendEvent({\n type: \"start\",\n total,\n message: `Renaming folder with ${itemsToUpdate.length} item(s)...`,\n });\n\n // Step 1: Rename the local folder and thumbnail folders\n if (hasLocalItem) {\n await fs.rename(absoluteOldPath, absoluteNewPath);\n\n // Also rename thumbnail directories\n // Thumbnails are at /images/folder/... so we need to rename those too\n const imagesDir = getPublicPath(\"/images\");\n const oldThumbFolder = path.join(imagesDir, oldRelativePath);\n const newThumbFolder = path.join(imagesDir, newRelativePath);\n try {\n await fs.access(oldThumbFolder);\n await fs.mkdir(path.dirname(newThumbFolder), { recursive: true });\n await fs.rename(oldThumbFolder, newThumbFolder);\n } catch {\n // Thumbnail folder might not exist\n }\n }\n sendEvent({\n type: \"progress\",\n current: 1,\n total,\n renamed: 1,\n message: \"Renamed folder\",\n });\n\n // Step 2: Update each item in the folder\n let renamed = 1;\n\n // Helper for cleanup on cancel\n const handleRenameCancel = async () => {\n await saveMeta(meta);\n // Clean up empty folders\n await deleteEmptyFolders(absoluteOldPath);\n const oldThumbFolder = path.join(\n getPublicPath(\"/images\"),\n oldRelativePath\n );\n await deleteEmptyFolders(oldThumbFolder);\n sendEvent({ type: \"complete\", renamed, newPath, cancelled: true });\n controller.close();\n };\n\n for (const item of itemsToUpdate) {\n // Check for cancellation\n if (isCancelled()) {\n await handleRenameCancel();\n return;\n }\n const { oldKey, newKey, entry } = item;\n const isInCloud = entry.c !== undefined;\n const fileCdnUrl = isInCloud ? cdnUrls[entry.c!] : undefined;\n const isInOurR2 = isInCloud && fileCdnUrl === publicUrl;\n const hasThumbnails = isProcessed(entry);\n\n // If in our R2, rename using server-side copy\n if (isInOurR2) {\n try {\n await moveInCdn(oldKey, newKey, hasThumbnails);\n\n // Clean up any local files that might exist after folder rename\n const localFilePath = getPublicPath(newKey);\n try {\n await fs.unlink(localFilePath);\n } catch {\n /* ignore */\n }\n if (hasThumbnails) {\n await deleteLocalThumbnails(newKey);\n }\n } catch (err) {\n console.error(`Failed to rename in CDN ${oldKey}:`, err);\n }\n }\n\n // Update meta entry\n delete meta[oldKey];\n meta[newKey] = entry;\n await saveMeta(meta);\n\n renamed++;\n sendEvent({\n type: \"progress\",\n current: renamed,\n total,\n renamed,\n message: `Renamed ${path.basename(newKey)}`,\n });\n }\n\n // Clean up empty folders (old folder location and old thumbnail location)\n await deleteEmptyFolders(absoluteOldPath);\n const oldThumbFolder = path.join(\n getPublicPath(\"/images\"),\n oldRelativePath\n );\n await deleteEmptyFolders(oldThumbFolder);\n\n sendEvent({ type: \"complete\", renamed, newPath });\n controller.close();\n return;\n }\n\n // ========== SINGLE FILE RENAME ==========\n const oldKey = \"/\" + oldRelativePath;\n const newKey = \"/\" + newRelativePath;\n const entry = meta[oldKey] as MetaEntry | undefined;\n const isInCloud = entry?.c !== undefined;\n const fileCdnUrl =\n isInCloud && entry?.c !== undefined ? cdnUrls[entry.c] : undefined;\n const isInOurR2 = isInCloud && fileCdnUrl === publicUrl;\n const hasThumbnails = entry ? isProcessed(entry) : false;\n\n sendEvent({ type: \"start\", total: 1, message: \"Renaming file...\" });\n\n // Handle cloud-only file (server-side rename in R2)\n if (isInOurR2 && !hasLocalItem) {\n await moveInCdn(oldKey, newKey, hasThumbnails);\n\n delete meta[oldKey];\n if (entry) meta[newKey] = entry;\n await saveMeta(meta);\n\n sendEvent({ type: \"complete\", renamed: 1, newPath });\n controller.close();\n return;\n }\n\n // Handle local file rename\n if (hasLocalItem) {\n await fs.rename(absoluteOldPath, absoluteNewPath);\n }\n\n if (isImagePath && entry) {\n const oldThumbPaths = getAllThumbnailPaths(oldKey);\n const newThumbPaths = getAllThumbnailPaths(newKey);\n\n for (let i = 0; i < oldThumbPaths.length; i++) {\n const oldThumbPath = getPublicPath(oldThumbPaths[i]);\n const newThumbPath = getPublicPath(newThumbPaths[i]);\n\n await fs.mkdir(path.dirname(newThumbPath), { recursive: true });\n\n try {\n await fs.rename(oldThumbPath, newThumbPath);\n } catch {\n /* skip */\n }\n }\n\n if (isInOurR2) {\n // Server-side rename in R2\n await moveInCdn(oldKey, newKey, hasThumbnails);\n\n // Clean up local files (they're now on CDN only)\n try {\n await fs.unlink(absoluteNewPath);\n } catch {\n /* ignore */\n }\n await deleteLocalThumbnails(newKey);\n }\n\n delete meta[oldKey];\n meta[newKey] = entry;\n await saveMeta(meta);\n }\n\n sendEvent({ type: \"complete\", renamed: 1, newPath });\n controller.close();\n } catch (error) {\n console.error(\"Rename stream error:\", error);\n sendEvent({ type: \"error\", message: \"Failed to rename\" });\n controller.close();\n }\n },\n });\n\n return streamResponse(stream);\n}\n\nexport async function handleMoveStream(request: Request) {\n const encoder = new TextEncoder();\n\n const stream = new ReadableStream({\n async start(controller) {\n const sendEvent = (data: object) => {\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\\n\\n`));\n };\n\n try {\n const { paths, destination, operationId } = await request.json();\n\n if (!paths || !Array.isArray(paths) || paths.length === 0) {\n sendEvent({ type: \"error\", message: \"Paths are required\" });\n controller.close();\n return;\n }\n\n if (!destination || typeof destination !== \"string\") {\n sendEvent({ type: \"error\", message: \"Destination is required\" });\n controller.close();\n return;\n }\n\n // Helper to check if operation was cancelled\n const isCancelled = () =>\n operationId ? isOperationCancelled(operationId) : false;\n\n const safeDestination = destination.replace(/\\.\\./g, \"\");\n const absoluteDestination = getWorkspacePath(safeDestination);\n\n if (!absoluteDestination.startsWith(getPublicPath())) {\n sendEvent({ type: \"error\", message: \"Invalid destination\" });\n controller.close();\n return;\n }\n\n // Ensure destination folder exists\n await fs.mkdir(absoluteDestination, { recursive: true });\n\n const meta = await loadMeta();\n const cdnUrls = getCdnUrls(meta);\n const r2PublicUrl =\n process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/$/, \"\") || \"\";\n\n const moved: string[] = [];\n const errors: string[] = [];\n const sourceFolders = new Set<string>();\n\n // Pre-calculate total files to move (expand folders and virtual folders)\n let totalFiles = 0;\n const expandedItems: Array<{\n itemPath: string;\n safePath: string;\n itemName: string;\n oldKey: string;\n newKey: string;\n newAbsolutePath: string;\n isVirtualFolder: boolean;\n virtualFolderItems?: Array<{\n oldKey: string;\n newKey: string;\n entry: MetaEntry;\n }>;\n }> = [];\n\n for (const itemPath of paths) {\n const safePath = itemPath.replace(/\\.\\./g, \"\");\n const itemName = path.basename(safePath);\n const oldRelativePath = safePath.replace(/^public\\/?/, \"\");\n const destWithoutPublic = safeDestination.replace(/^public\\/?/, \"\");\n const newRelativePath = destWithoutPublic\n ? path.join(destWithoutPublic, itemName)\n : itemName;\n const oldKey = \"/\" + oldRelativePath;\n const newKey = \"/\" + newRelativePath;\n const newAbsolutePath = path.join(absoluteDestination, itemName);\n const absolutePath = getWorkspacePath(safePath);\n\n // Check if it's a physical item\n let hasLocalItem = false;\n let isDirectory = false;\n try {\n const stats = await fs.stat(absolutePath);\n hasLocalItem = true;\n isDirectory = stats.isDirectory();\n } catch {\n // Check if it's a virtual folder\n }\n\n if (hasLocalItem && isDirectory) {\n // Count files in physical directory\n const countFilesRecursive = async (\n dir: string\n ): Promise<number> => {\n let count = 0;\n const entries = await fs.readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory()) {\n count += await countFilesRecursive(\n path.join(dir, entry.name)\n );\n } else {\n count++;\n }\n }\n return count;\n };\n const localFileCount = await countFilesRecursive(absolutePath);\n\n // Also count cloud-only files in meta that aren't local\n const folderPrefix = oldKey + \"/\";\n let cloudOnlyCount = 0;\n for (const metaKey of Object.keys(meta)) {\n if (metaKey.startsWith(folderPrefix)) {\n const relPath = metaKey.slice(folderPrefix.length);\n const localPath = path.join(absolutePath, relPath);\n try {\n await fs.access(localPath);\n // File exists locally, already counted\n } catch {\n // Cloud-only file\n cloudOnlyCount++;\n }\n }\n }\n\n totalFiles += localFileCount + cloudOnlyCount;\n expandedItems.push({\n itemPath,\n safePath,\n itemName,\n oldKey,\n newKey,\n newAbsolutePath,\n isVirtualFolder: false,\n });\n } else if (!hasLocalItem) {\n // Check for virtual folder\n const folderPrefix = oldKey + \"/\";\n const virtualItems: Array<{\n oldKey: string;\n newKey: string;\n entry: MetaEntry;\n }> = [];\n for (const [key, metaEntry] of Object.entries(meta)) {\n if (\n key.startsWith(folderPrefix) &&\n metaEntry &&\n typeof metaEntry === \"object\"\n ) {\n const relativePath = key.slice(folderPrefix.length);\n const destNewKey = newKey + \"/\" + relativePath;\n virtualItems.push({\n oldKey: key,\n newKey: destNewKey,\n entry: metaEntry as MetaEntry,\n });\n }\n }\n if (virtualItems.length > 0) {\n totalFiles += virtualItems.length;\n expandedItems.push({\n itemPath,\n safePath,\n itemName,\n oldKey,\n newKey,\n newAbsolutePath,\n isVirtualFolder: true,\n virtualFolderItems: virtualItems,\n });\n // Track source folder for cleanup\n sourceFolders.add(absolutePath);\n } else {\n // Single file (local or cloud)\n totalFiles++;\n expandedItems.push({\n itemPath,\n safePath,\n itemName,\n oldKey,\n newKey,\n newAbsolutePath,\n isVirtualFolder: false,\n });\n }\n } else {\n // Single local file\n totalFiles++;\n expandedItems.push({\n itemPath,\n safePath,\n itemName,\n oldKey,\n newKey,\n newAbsolutePath,\n isVirtualFolder: false,\n });\n }\n }\n\n sendEvent({ type: \"start\", total: totalFiles });\n let processedFiles = 0;\n\n // Track individual files moved (for accurate count on cancel)\n let filesMoved = 0;\n\n // Helper to do cleanup and send cancel complete\n const handleCancel = async () => {\n await saveMeta(meta);\n // Clean up empty source folders\n for (const folder of sourceFolders) {\n await deleteEmptyFolders(folder);\n }\n // Clean up destination if it became empty\n await deleteEmptyFolders(absoluteDestination);\n sendEvent({\n type: \"complete\",\n moved: filesMoved,\n errors: errors.length,\n errorMessages: errors,\n cancelled: true,\n });\n controller.close();\n };\n\n for (const expandedItem of expandedItems) {\n // Check for cancellation before processing each item\n if (isCancelled()) {\n await handleCancel();\n return;\n }\n\n const {\n itemPath,\n safePath,\n itemName,\n oldKey,\n newKey,\n newAbsolutePath,\n isVirtualFolder,\n virtualFolderItems,\n } = expandedItem;\n\n // Handle virtual folder\n if (isVirtualFolder && virtualFolderItems) {\n for (const vItem of virtualFolderItems) {\n // Check for cancellation before processing each virtual item\n if (isCancelled()) {\n await handleCancel();\n return;\n }\n const itemEntry = vItem.entry;\n const isItemInCloud = itemEntry.c !== undefined;\n const itemCdnUrl = isItemInCloud\n ? cdnUrls[itemEntry.c!]\n : undefined;\n const isItemInR2 = isItemInCloud && itemCdnUrl === r2PublicUrl;\n const itemHasThumbnails = isProcessed(itemEntry);\n\n let vItemMoved = false;\n\n if (isItemInR2) {\n try {\n // Server-side copy+delete in R2 (no download/upload needed)\n await moveInCdn(\n vItem.oldKey,\n vItem.newKey,\n itemHasThumbnails\n );\n vItemMoved = true;\n filesMoved++;\n } catch (err) {\n console.error(\n `Failed to move cloud item ${vItem.oldKey}:`,\n err\n );\n // File doesn't exist on CDN - remove orphaned meta entry\n delete meta[vItem.oldKey];\n await saveMeta(meta);\n }\n }\n\n // Only update meta if file was successfully moved\n if (vItemMoved) {\n delete meta[vItem.oldKey];\n meta[vItem.newKey] = itemEntry;\n await saveMeta(meta);\n // Track source folder for cleanup\n const oldAbsPath = getPublicPath(vItem.oldKey);\n sourceFolders.add(path.dirname(oldAbsPath));\n }\n\n processedFiles++;\n sendEvent({\n type: \"progress\",\n current: processedFiles,\n total: totalFiles,\n moved: filesMoved,\n percent: Math.round((processedFiles / totalFiles) * 100),\n currentFile: path.basename(vItem.newKey),\n });\n }\n\n // Clean up temp folders created for virtual folder cloud file processing\n // Clean up the new folder location (where temp files were downloaded)\n const newFolderPath = getPublicPath(newKey);\n await deleteEmptyFolders(newFolderPath);\n // Clean up thumbnail folder\n const newThumbFolder = path.join(\n getPublicPath(\"images\"),\n newKey.slice(1)\n );\n await deleteEmptyFolders(newThumbFolder);\n // Track old folder for cleanup\n const oldFolderPath = getPublicPath(oldKey);\n sourceFolders.add(oldFolderPath);\n\n moved.push(itemPath);\n continue;\n }\n\n // Check if destination already exists in meta\n if (meta[newKey]) {\n errors.push(`${itemName} already exists in destination`);\n processedFiles++;\n sendEvent({\n type: \"progress\",\n current: processedFiles,\n total: totalFiles,\n moved: moved.length,\n percent: Math.round((processedFiles / totalFiles) * 100),\n currentFile: itemName,\n });\n continue;\n }\n\n const entry = meta[oldKey] as MetaEntry | undefined;\n const isImage = isImageFile(itemName);\n\n // Determine if cloud or remote\n const isInCloud = entry?.c !== undefined;\n const fileCdnUrl =\n isInCloud && entry.c !== undefined ? cdnUrls[entry.c] : undefined;\n const isRemote =\n isInCloud && (!r2PublicUrl || fileCdnUrl !== r2PublicUrl);\n const isPushedToR2 =\n isInCloud && r2PublicUrl && fileCdnUrl === r2PublicUrl;\n const hasProcessedThumbnails = isProcessed(entry);\n\n try {\n // Track source folder for cleanup\n const sourceFolder = path.dirname(getWorkspacePath(safePath));\n sourceFolders.add(sourceFolder);\n\n if (isRemote) {\n // ===== REMOTE FILE (external CDN) =====\n // Download to local with new path\n const remoteUrl = `${fileCdnUrl}${oldKey}`;\n const buffer = await downloadFromRemoteUrl(remoteUrl);\n\n await fs.mkdir(path.dirname(newAbsolutePath), {\n recursive: true,\n });\n await fs.writeFile(newAbsolutePath, buffer);\n\n // Create new entry without CDN reference (now local)\n const newEntry: MetaEntry = {\n o: entry?.o,\n b: entry?.b,\n };\n delete meta[oldKey];\n meta[newKey] = newEntry;\n await saveMeta(meta);\n moved.push(itemPath);\n filesMoved++;\n processedFiles++;\n sendEvent({\n type: \"progress\",\n current: processedFiles,\n total: totalFiles,\n moved: filesMoved,\n percent: Math.round((processedFiles / totalFiles) * 100),\n currentFile: itemName,\n });\n } else if (isPushedToR2) {\n // ===== CLOUD FILE (R2) - server-side move =====\n // Works for both images and non-images\n await moveInCdn(oldKey, newKey, hasProcessedThumbnails);\n\n // Keep same entry, just update the key\n delete meta[oldKey];\n if (entry) {\n meta[newKey] = entry;\n }\n await saveMeta(meta);\n moved.push(itemPath);\n filesMoved++;\n processedFiles++;\n sendEvent({\n type: \"progress\",\n current: processedFiles,\n total: totalFiles,\n moved: filesMoved,\n percent: Math.round((processedFiles / totalFiles) * 100),\n currentFile: itemName,\n });\n } else {\n // ===== LOCAL FILE =====\n const absolutePath = getWorkspacePath(safePath);\n\n if (absoluteDestination.startsWith(absolutePath + path.sep)) {\n errors.push(`Cannot move ${itemName} into itself`);\n processedFiles++;\n sendEvent({\n type: \"progress\",\n current: processedFiles,\n total: totalFiles,\n moved: filesMoved,\n percent: Math.round((processedFiles / totalFiles) * 100),\n currentFile: itemName,\n });\n continue;\n }\n\n // Check if local file/folder exists\n try {\n await fs.access(absolutePath);\n } catch {\n errors.push(`${itemName} not found`);\n processedFiles++;\n sendEvent({\n type: \"progress\",\n current: processedFiles,\n total: totalFiles,\n moved: filesMoved,\n percent: Math.round((processedFiles / totalFiles) * 100),\n currentFile: itemName,\n });\n continue;\n }\n\n try {\n await fs.access(newAbsolutePath);\n errors.push(`${itemName} already exists in destination`);\n processedFiles++;\n sendEvent({\n type: \"progress\",\n current: processedFiles,\n total: totalFiles,\n moved: filesMoved,\n percent: Math.round((processedFiles / totalFiles) * 100),\n currentFile: itemName,\n });\n continue;\n } catch {\n // Good\n }\n\n const stats = await fs.stat(absolutePath);\n\n if (stats.isFile()) {\n // ===== SINGLE LOCAL FILE =====\n await fs.mkdir(path.dirname(newAbsolutePath), {\n recursive: true,\n });\n await fs.rename(absolutePath, newAbsolutePath);\n\n if (isImage && entry) {\n const oldThumbPaths = getAllThumbnailPaths(oldKey);\n const newThumbPaths = getAllThumbnailPaths(newKey);\n\n for (let j = 0; j < oldThumbPaths.length; j++) {\n const oldThumbPath = getPublicPath(oldThumbPaths[j]);\n const newThumbPath = getPublicPath(newThumbPaths[j]);\n\n try {\n await fs.access(oldThumbPath);\n sourceFolders.add(path.dirname(oldThumbPath));\n await fs.mkdir(path.dirname(newThumbPath), {\n recursive: true,\n });\n await fs.rename(oldThumbPath, newThumbPath);\n } catch {\n // Thumbnail doesn't exist\n }\n }\n\n // Check if file was synced to cloud - needs re-upload with new key\n const fileIsInCloud = entry.c !== undefined;\n const fileCdnUrl = fileIsInCloud\n ? cdnUrls[entry.c!]\n : undefined;\n const fileIsInR2 =\n fileIsInCloud && fileCdnUrl === r2PublicUrl;\n const fileHasThumbs = isProcessed(entry);\n\n if (fileIsInR2) {\n // Re-upload with new key\n await deleteFromCdn(oldKey, fileHasThumbs);\n await uploadOriginalToCdn(newKey);\n if (fileHasThumbs) {\n await uploadToCdn(newKey);\n }\n }\n\n delete meta[oldKey];\n meta[newKey] = entry;\n await saveMeta(meta);\n }\n\n moved.push(itemPath);\n filesMoved++;\n processedFiles++;\n sendEvent({\n type: \"progress\",\n current: processedFiles,\n total: totalFiles,\n moved: filesMoved,\n percent: Math.round((processedFiles / totalFiles) * 100),\n currentFile: itemName,\n });\n } else if (stats.isDirectory()) {\n // ===== LOCAL DIRECTORY - iterate through files =====\n const oldPrefix = oldKey + \"/\";\n const newPrefix = newKey + \"/\";\n\n // Collect all files in directory (local + cloud-only from meta)\n const localFiles: Array<{\n relativePath: string;\n isImage: boolean;\n }> = [];\n\n const collectLocalFiles = async (\n dir: string,\n relativeDir: string\n ) => {\n const entries = await fs.readdir(dir, {\n withFileTypes: true,\n });\n for (const dirEntry of entries) {\n const entryRelPath = relativeDir\n ? `${relativeDir}/${dirEntry.name}`\n : dirEntry.name;\n if (dirEntry.isDirectory()) {\n await collectLocalFiles(\n path.join(dir, dirEntry.name),\n entryRelPath\n );\n } else {\n localFiles.push({\n relativePath: entryRelPath,\n isImage: isImageFile(dirEntry.name),\n });\n }\n }\n };\n await collectLocalFiles(absolutePath, \"\");\n\n // Also find cloud-only files from meta that aren't local\n const cloudOnlyFiles: Array<{\n oldKey: string;\n newKey: string;\n entry: MetaEntry;\n }> = [];\n for (const [metaKey, metaEntry] of Object.entries(meta)) {\n if (\n metaKey.startsWith(oldPrefix) &&\n metaEntry &&\n typeof metaEntry === \"object\"\n ) {\n const relPath = metaKey.slice(oldPrefix.length);\n const localPath = path.join(absolutePath, relPath);\n try {\n await fs.access(localPath);\n // File exists locally, will be handled by localFiles\n } catch {\n // Cloud-only file\n cloudOnlyFiles.push({\n oldKey: metaKey,\n newKey: newPrefix + relPath,\n entry: metaEntry as MetaEntry,\n });\n }\n }\n }\n\n // Process each local file\n for (const localFile of localFiles) {\n // Check for cancellation\n if (isCancelled()) {\n await handleCancel();\n return;\n }\n const fileOldPath = path.join(\n absolutePath,\n localFile.relativePath\n );\n const fileNewPath = path.join(\n newAbsolutePath,\n localFile.relativePath\n );\n const fileOldKey = oldPrefix + localFile.relativePath;\n const fileNewKey = newPrefix + localFile.relativePath;\n const fileEntry = meta[fileOldKey] as MetaEntry | undefined;\n\n // Track source folder\n sourceFolders.add(path.dirname(fileOldPath));\n\n // Move the file\n await fs.mkdir(path.dirname(fileNewPath), {\n recursive: true,\n });\n await fs.rename(fileOldPath, fileNewPath);\n filesMoved++;\n\n if (localFile.isImage && fileEntry) {\n // Move thumbnails\n const oldThumbPaths = getAllThumbnailPaths(fileOldKey);\n const newThumbPaths = getAllThumbnailPaths(fileNewKey);\n\n for (let t = 0; t < oldThumbPaths.length; t++) {\n const oldThumbPath = getPublicPath(oldThumbPaths[t]);\n const newThumbPath = getPublicPath(newThumbPaths[t]);\n try {\n await fs.access(oldThumbPath);\n sourceFolders.add(path.dirname(oldThumbPath));\n await fs.mkdir(path.dirname(newThumbPath), {\n recursive: true,\n });\n await fs.rename(oldThumbPath, newThumbPath);\n } catch {\n /* skip */\n }\n }\n\n // Check if synced to cloud - move in CDN with new key\n const fileIsInCloud = fileEntry.c !== undefined;\n const fileCdnUrl = fileIsInCloud\n ? cdnUrls[fileEntry.c!]\n : undefined;\n const fileIsInR2 =\n fileIsInCloud && fileCdnUrl === r2PublicUrl;\n const fileHasThumbs = isProcessed(fileEntry);\n\n if (fileIsInR2) {\n // Server-side copy+delete in R2 (faster than re-upload)\n await moveInCdn(fileOldKey, fileNewKey, fileHasThumbs);\n }\n\n delete meta[fileOldKey];\n meta[fileNewKey] = fileEntry;\n await saveMeta(meta);\n }\n\n processedFiles++;\n sendEvent({\n type: \"progress\",\n current: processedFiles,\n total: totalFiles,\n moved: filesMoved,\n percent: Math.round((processedFiles / totalFiles) * 100),\n currentFile: path.basename(localFile.relativePath),\n });\n }\n\n // Process cloud-only files within the directory\n for (const cloudFile of cloudOnlyFiles) {\n // Check for cancellation\n if (isCancelled()) {\n await handleCancel();\n return;\n }\n const cloudEntry = cloudFile.entry;\n const cloudIsInCloud = cloudEntry.c !== undefined;\n const cloudCdnUrl = cloudIsInCloud\n ? cdnUrls[cloudEntry.c!]\n : undefined;\n const cloudIsInR2 =\n cloudIsInCloud && cloudCdnUrl === r2PublicUrl;\n const cloudHasThumbs = isProcessed(cloudEntry);\n\n let cloudFileMoved = false;\n\n if (cloudIsInR2) {\n try {\n // Server-side copy+delete in R2 (no download/upload needed)\n await moveInCdn(\n cloudFile.oldKey,\n cloudFile.newKey,\n cloudHasThumbs\n );\n cloudFileMoved = true;\n filesMoved++;\n } catch (err) {\n console.error(\n `Failed to move cloud file ${cloudFile.oldKey}:`,\n err\n );\n // File doesn't exist on CDN - remove from meta since it's orphaned\n delete meta[cloudFile.oldKey];\n await saveMeta(meta);\n }\n }\n\n // Only update meta if file was successfully moved\n if (cloudFileMoved) {\n delete meta[cloudFile.oldKey];\n meta[cloudFile.newKey] = cloudEntry;\n await saveMeta(meta);\n }\n\n processedFiles++;\n sendEvent({\n type: \"progress\",\n current: processedFiles,\n total: totalFiles,\n moved: filesMoved,\n percent: Math.round((processedFiles / totalFiles) * 100),\n currentFile: path.basename(cloudFile.newKey),\n });\n }\n\n // Clean up old empty source folder\n sourceFolders.add(absolutePath);\n\n // Clean up old thumbnail folder\n const oldThumbRelPath = oldKey.slice(1);\n const oldThumbFolder = path.join(\n getPublicPath(\"images\"),\n oldThumbRelPath\n );\n sourceFolders.add(oldThumbFolder);\n\n moved.push(itemPath);\n }\n }\n } catch (err) {\n console.error(`Failed to move ${itemName}:`, err);\n errors.push(`Failed to move ${itemName}`);\n processedFiles++;\n sendEvent({\n type: \"progress\",\n current: processedFiles,\n total: totalFiles,\n moved: filesMoved,\n percent: Math.round((processedFiles / totalFiles) * 100),\n currentFile: itemName,\n });\n }\n }\n\n await saveMeta(meta);\n\n // Clean up empty source folders\n for (const folder of sourceFolders) {\n await deleteEmptyFolders(folder);\n }\n\n // Clean up destination folder if it was created but is now empty\n // (happens when moving virtual folders with server-side copy)\n await deleteEmptyFolders(absoluteDestination);\n\n sendEvent({\n type: \"complete\",\n moved: filesMoved,\n errors: errors.length,\n errorMessages: errors,\n });\n } catch (error) {\n console.error(\"Failed to move:\", error);\n sendEvent({ type: \"error\", message: \"Failed to move items\" });\n } finally {\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n}\n\nexport async function handleMove(request: Request) {\n try {\n const { paths, destination } = await request.json();\n\n if (!paths || !Array.isArray(paths) || paths.length === 0) {\n return jsonResponse({ error: \"Paths are required\" }, { status: 400 });\n }\n\n if (!destination || typeof destination !== \"string\") {\n return jsonResponse(\n { error: \"Destination is required\" },\n { status: 400 }\n );\n }\n\n const safeDestination = destination.replace(/\\.\\./g, \"\");\n const absoluteDestination = getWorkspacePath(safeDestination);\n\n if (!absoluteDestination.startsWith(getPublicPath())) {\n return jsonResponse({ error: \"Invalid destination\" }, { status: 400 });\n }\n\n // Ensure destination folder exists (create if needed)\n await fs.mkdir(absoluteDestination, { recursive: true });\n\n const moved: string[] = [];\n const errors: string[] = [];\n const sourceFolders = new Set<string>();\n const meta = await loadMeta();\n const cdnUrls = getCdnUrls(meta);\n const r2PublicUrl =\n process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/$/, \"\") || \"\";\n let metaChanged = false;\n\n for (const itemPath of paths) {\n const safePath = itemPath.replace(/\\.\\./g, \"\");\n const itemName = path.basename(safePath);\n const newAbsolutePath = path.join(absoluteDestination, itemName);\n\n // Build meta keys\n const oldRelativePath = safePath.replace(/^public\\/?/, \"\");\n const destWithoutPublic = safeDestination.replace(/^public\\/?/, \"\");\n const newRelativePath = destWithoutPublic\n ? path.join(destWithoutPublic, itemName)\n : itemName;\n const oldKey = \"/\" + oldRelativePath;\n const newKey = \"/\" + newRelativePath;\n\n // Track source folder for cleanup\n sourceFolders.add(path.dirname(getWorkspacePath(safePath)));\n\n // Check if destination already exists in meta\n if (meta[newKey]) {\n errors.push(`${itemName} already exists in destination`);\n continue;\n }\n\n const entry = meta[oldKey] as MetaEntry | undefined;\n const isImage = isImageFile(itemName);\n\n // Determine if cloud or remote\n const isInCloud = entry?.c !== undefined;\n const fileCdnUrl =\n isInCloud && entry.c !== undefined ? cdnUrls[entry.c] : undefined;\n const isRemote =\n isInCloud && (!r2PublicUrl || fileCdnUrl !== r2PublicUrl);\n const isPushedToR2 =\n isInCloud && r2PublicUrl && fileCdnUrl === r2PublicUrl;\n const hasProcessedThumbnails = isProcessed(entry);\n\n try {\n if (isRemote) {\n // ===== REMOTE FILE: Download from external URL, save locally, remove c =====\n const remoteUrl = `${fileCdnUrl}${oldKey}`;\n const buffer = await downloadFromRemoteUrl(remoteUrl);\n\n // Save to new local location\n await fs.mkdir(path.dirname(newAbsolutePath), { recursive: true });\n await fs.writeFile(newAbsolutePath, buffer);\n\n // Update meta: remove c (now local), keep other properties\n const newEntry: MetaEntry = {\n o: entry?.o,\n b: entry?.b,\n // Don't copy thumbnail dims since remote files don't have local thumbnails\n // Don't copy c since it's now local\n };\n delete meta[oldKey];\n meta[newKey] = newEntry;\n metaChanged = true;\n moved.push(itemPath);\n } else if (isPushedToR2) {\n // ===== CLOUD FILE (R2): Server-side move =====\n await moveInCdn(oldKey, newKey, hasProcessedThumbnails);\n\n // Update meta with same entry, new key\n delete meta[oldKey];\n if (entry) {\n meta[newKey] = entry;\n }\n metaChanged = true;\n moved.push(itemPath);\n } else {\n // ===== LOCAL FILE: Use standard fs.rename =====\n const absolutePath = getWorkspacePath(safePath);\n\n if (absoluteDestination.startsWith(absolutePath + path.sep)) {\n errors.push(`Cannot move ${itemName} into itself`);\n continue;\n }\n\n try {\n await fs.access(absolutePath);\n } catch {\n errors.push(`${itemName} not found`);\n continue;\n }\n\n try {\n await fs.access(newAbsolutePath);\n errors.push(`${itemName} already exists in destination`);\n continue;\n } catch {\n // Good - doesn't exist\n }\n\n await fs.rename(absolutePath, newAbsolutePath);\n\n const stats = await fs.stat(newAbsolutePath);\n if (stats.isFile() && isImage && entry) {\n // Move local thumbnails\n const oldThumbPaths = getAllThumbnailPaths(oldKey);\n const newThumbPaths = getAllThumbnailPaths(newKey);\n\n for (let i = 0; i < oldThumbPaths.length; i++) {\n const oldThumbPath = getPublicPath(oldThumbPaths[i]);\n const newThumbPath = getPublicPath(newThumbPaths[i]);\n\n try {\n // Check if thumbnail exists before trying to move\n await fs.access(oldThumbPath);\n\n // Track thumbnail source folder for cleanup\n sourceFolders.add(path.dirname(oldThumbPath));\n\n // Create destination folder and move thumbnail\n await fs.mkdir(path.dirname(newThumbPath), { recursive: true });\n await fs.rename(oldThumbPath, newThumbPath);\n } catch {\n // Thumbnail doesn't exist, skip\n }\n }\n\n delete meta[oldKey];\n meta[newKey] = entry;\n metaChanged = true;\n } else if (stats.isDirectory()) {\n // Move folder: update all meta entries under this folder\n const oldPrefix = oldKey + \"/\";\n const newPrefix = newKey + \"/\";\n\n for (const key of Object.keys(meta)) {\n if (key.startsWith(oldPrefix)) {\n const newMetaKey = newPrefix + key.slice(oldPrefix.length);\n meta[newMetaKey] = meta[key];\n delete meta[key];\n metaChanged = true;\n }\n }\n\n // Also move the thumbnail folder\n const imagesDir = getPublicPath(\"images\");\n const oldThumbFolder = path.join(imagesDir, oldRelativePath);\n const newThumbFolder = path.join(imagesDir, newRelativePath);\n try {\n await fs.access(oldThumbFolder);\n // Track old thumbnail folder for cleanup\n sourceFolders.add(oldThumbFolder);\n await fs.mkdir(path.dirname(newThumbFolder), { recursive: true });\n await fs.rename(oldThumbFolder, newThumbFolder);\n } catch {\n // Thumbnail folder might not exist\n }\n }\n\n moved.push(itemPath);\n }\n } catch (err) {\n console.error(`Failed to move ${itemName}:`, err);\n errors.push(`Failed to move ${itemName}`);\n }\n }\n\n if (metaChanged) {\n await saveMeta(meta);\n }\n\n // Clean up empty source folders\n for (const folder of sourceFolders) {\n await deleteEmptyFolders(folder);\n }\n\n // Clean up destination folder if it was created but is now empty\n // (happens when moving virtual folders with server-side copy)\n await deleteEmptyFolders(absoluteDestination);\n\n return jsonResponse({\n success: errors.length === 0,\n moved,\n errors: errors.length > 0 ? errors : undefined,\n });\n } catch (error) {\n console.error(\"Failed to move:\", error);\n return jsonResponse({ error: \"Failed to move items\" }, { status: 500 });\n }\n}\n","import { promises as fs } from 'fs'\nimport path from 'path'\nimport { getPublicPath } from '../../config'\n\n/**\n * Check if a file is a hidden/system file that should be ignored\n * - Mac/Linux: files starting with .\n * - Windows: Thumbs.db, desktop.ini, etc.\n */\nexport function isHiddenOrSystemFile(filename: string): boolean {\n // Hidden files on Mac/Linux start with .\n if (filename.startsWith('.')) return true\n \n // Windows system files\n const windowsSystemFiles = ['thumbs.db', 'desktop.ini', 'ehthumbs.db', 'ehthumbs_vista.db']\n if (windowsSystemFiles.includes(filename.toLowerCase())) return true\n \n return false\n}\n\n/**\n * Recursively delete empty folders starting from the given path\n * Stops at the public folder boundary\n * Includes the images folder - it can be deleted if empty and will be recreated when needed\n * Ignores hidden and system files (deletes them if they're the only contents)\n */\nexport async function deleteEmptyFolders(folderPath: string): Promise<void> {\n const publicPath = getPublicPath()\n \n // Normalize paths for comparison\n const normalizedFolder = path.resolve(folderPath)\n const normalizedPublic = path.resolve(publicPath)\n \n // Don't delete the public folder itself\n if (normalizedFolder === normalizedPublic) {\n return\n }\n \n // Check if folder is inside public\n if (!normalizedFolder.startsWith(normalizedPublic)) {\n return\n }\n \n try {\n const entries = await fs.readdir(folderPath)\n \n // Filter out hidden/system files\n const meaningfulEntries = entries.filter(e => !isHiddenOrSystemFile(e))\n \n // If folder only contains hidden/system files (or is empty), delete it\n if (meaningfulEntries.length === 0) {\n // First delete any hidden/system files\n for (const entry of entries) {\n if (isHiddenOrSystemFile(entry)) {\n try {\n await fs.unlink(path.join(folderPath, entry))\n } catch {\n // Ignore deletion errors for system files\n }\n }\n }\n \n // Now delete the folder\n await fs.rmdir(folderPath)\n \n // Recursively check parent folder\n const parentFolder = path.dirname(folderPath)\n await deleteEmptyFolders(parentFolder)\n }\n } catch {\n // Folder doesn't exist or can't be read, ignore\n }\n}\n\n/**\n * Ensure a folder exists, creating it if necessary\n */\nexport async function ensureFolderExists(folderPath: string): Promise<void> {\n try {\n await fs.mkdir(folderPath, { recursive: true })\n } catch {\n // Already exists or can't be created\n }\n}\n\n/**\n * Recursively clean up all empty folders within a directory\n * Also deletes the directory itself if it becomes empty\n * Ignores hidden/system files (deletes them if they're the only contents)\n */\nexport async function cleanupEmptyFoldersRecursive(dir: string): Promise<boolean> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true })\n let isEmpty = true\n \n for (const entry of entries) {\n if (entry.isDirectory()) {\n const subDirEmpty = await cleanupEmptyFoldersRecursive(path.join(dir, entry.name))\n if (!subDirEmpty) isEmpty = false\n } else if (!isHiddenOrSystemFile(entry.name)) {\n // Non-hidden file exists\n isEmpty = false\n }\n }\n \n // Delete empty folder\n if (isEmpty) {\n // First delete any hidden/system files\n for (const entry of entries) {\n if (!entry.isDirectory() && isHiddenOrSystemFile(entry.name)) {\n try {\n await fs.unlink(path.join(dir, entry.name))\n } catch {\n // Ignore deletion errors\n }\n }\n }\n await fs.rmdir(dir)\n }\n \n return isEmpty\n } catch {\n return true\n }\n}\n","import { promises as fs } from \"fs\";\nimport path from \"path\";\nimport {\n S3Client,\n PutObjectCommand,\n DeleteObjectCommand,\n} from \"@aws-sdk/client-s3\";\nimport { jsonResponse } from \"./utils/response\";\nimport { getAllThumbnailPaths, isProcessed, type MetaEntry } from \"../types\";\nimport {\n loadMeta,\n saveMeta,\n isImageFile,\n getContentType,\n processImage,\n downloadFromCdn,\n uploadToCdn,\n uploadOriginalToCdn,\n deleteLocalThumbnails,\n deleteThumbnailsFromCdn,\n deleteOriginalFromCdn,\n getOrAddCdnIndex,\n getFileEntries,\n getMetaEntry,\n getCdnUrls,\n downloadFromRemoteUrl,\n} from \"./utils\";\nimport { getPublicPath } from \"../config\";\nimport {\n deleteEmptyFolders,\n cleanupEmptyFoldersRecursive,\n} from \"./utils/folders\";\n\n// Global cancellation tokens for streaming operations\nconst cancelledOperations = new Set<string>();\n\nexport function cancelOperation(operationId: string) {\n cancelledOperations.add(operationId);\n // Clean up after 60 seconds\n setTimeout(() => cancelledOperations.delete(operationId), 60000);\n}\n\nexport function isOperationCancelled(operationId: string): boolean {\n return cancelledOperations.has(operationId);\n}\n\nexport function clearCancelledOperation(operationId: string) {\n cancelledOperations.delete(operationId);\n}\n\nexport async function handleSync(request: Request) {\n const accountId = process.env.CLOUDFLARE_R2_ACCOUNT_ID;\n const accessKeyId = process.env.CLOUDFLARE_R2_ACCESS_KEY_ID;\n const secretAccessKey = process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY;\n const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;\n const publicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/\\s*$/, \"\");\n\n if (\n !accountId ||\n !accessKeyId ||\n !secretAccessKey ||\n !bucketName ||\n !publicUrl\n ) {\n return jsonResponse(\n {\n error: \"R2 not configured. Set CLOUDFLARE_R2_* environment variables.\",\n },\n { status: 400 }\n );\n }\n\n try {\n const { imageKeys } = (await request.json()) as { imageKeys: string[] };\n\n if (!imageKeys || !Array.isArray(imageKeys) || imageKeys.length === 0) {\n return jsonResponse({ error: \"No image keys provided\" }, { status: 400 });\n }\n\n const meta = await loadMeta();\n const cdnUrls = getCdnUrls(meta);\n\n // Get or add CDN URL to the _cdns array\n const cdnIndex = getOrAddCdnIndex(meta, publicUrl);\n\n const r2 = new S3Client({\n region: \"auto\",\n endpoint: `https://${accountId}.r2.cloudflarestorage.com`,\n credentials: { accessKeyId, secretAccessKey },\n });\n\n const pushed: string[] = [];\n const alreadyPushed: string[] = [];\n const errors: string[] = [];\n const sourceFolders = new Set<string>();\n\n for (let imageKey of imageKeys) {\n // Normalize key to have leading /\n if (!imageKey.startsWith(\"/\")) {\n imageKey = `/${imageKey}`;\n }\n\n const entry = getMetaEntry(meta, imageKey);\n if (!entry) {\n errors.push(`Image not found in meta: ${imageKey}. Run Scan first.`);\n continue;\n }\n\n // Check if already pushed to our R2\n const existingCdnUrl =\n entry.c !== undefined ? cdnUrls[entry.c] : undefined;\n const isAlreadyInOurR2 = existingCdnUrl === publicUrl;\n\n if (isAlreadyInOurR2) {\n alreadyPushed.push(imageKey);\n continue;\n }\n\n // Check if this is a remote image (in another CDN)\n const isRemote = entry.c !== undefined && existingCdnUrl !== publicUrl;\n\n try {\n let originalBuffer: Buffer;\n\n if (isRemote) {\n // Download from remote URL\n const remoteUrl = `${existingCdnUrl}${imageKey}`;\n originalBuffer = await downloadFromRemoteUrl(remoteUrl);\n } else {\n // Read from local file\n const originalLocalPath = getPublicPath(imageKey);\n try {\n originalBuffer = await fs.readFile(originalLocalPath);\n } catch {\n errors.push(`Original file not found: ${imageKey}`);\n continue;\n }\n }\n\n // Upload original to R2\n await r2.send(\n new PutObjectCommand({\n Bucket: bucketName,\n Key: imageKey.replace(/^\\//, \"\"),\n Body: originalBuffer,\n ContentType: getContentType(imageKey),\n })\n );\n\n // Upload thumbnails (only if processed locally, not for remote imports)\n if (!isRemote && isProcessed(entry)) {\n for (const thumbPath of getAllThumbnailPaths(imageKey)) {\n const localPath = getPublicPath(thumbPath);\n try {\n const fileBuffer = await fs.readFile(localPath);\n await r2.send(\n new PutObjectCommand({\n Bucket: bucketName,\n Key: thumbPath.replace(/^\\//, \"\"),\n Body: fileBuffer,\n ContentType: getContentType(thumbPath),\n })\n );\n } catch {\n // Thumbnail might not exist\n }\n }\n }\n\n entry.c = cdnIndex;\n\n // Delete local files (only for non-remote, local images being pushed)\n if (!isRemote) {\n const originalLocalPath = getPublicPath(imageKey);\n\n // Track source folder for cleanup\n sourceFolders.add(path.dirname(originalLocalPath));\n\n // Delete local thumbnails\n for (const thumbPath of getAllThumbnailPaths(imageKey)) {\n const localPath = getPublicPath(thumbPath);\n // Track thumbnail folder too\n sourceFolders.add(path.dirname(localPath));\n try {\n await fs.unlink(localPath);\n } catch {\n /* ignore */\n }\n }\n\n // Delete local original\n try {\n await fs.unlink(originalLocalPath);\n } catch {\n /* ignore */\n }\n }\n\n // Save meta after each successful push\n await saveMeta(meta);\n\n pushed.push(imageKey);\n } catch (error) {\n console.error(`Failed to push ${imageKey}:`, error);\n errors.push(`Failed to push: ${imageKey}`);\n }\n }\n\n // Clean up empty source folders\n for (const folder of sourceFolders) {\n await deleteEmptyFolders(folder);\n }\n\n return jsonResponse({\n success: true,\n pushed,\n alreadyPushed: alreadyPushed.length > 0 ? alreadyPushed : undefined,\n errors: errors.length > 0 ? errors : undefined,\n });\n } catch (error) {\n console.error(\"Failed to push:\", error);\n return jsonResponse({ error: \"Failed to push to CDN\" }, { status: 500 });\n }\n}\n\n/**\n * Push files to CDN (streaming version with progress)\n * Handles local files, remote files, and already-pushed files\n */\nexport async function handleSyncStream(request: Request) {\n const accountId = process.env.CLOUDFLARE_R2_ACCOUNT_ID;\n const accessKeyId = process.env.CLOUDFLARE_R2_ACCESS_KEY_ID;\n const secretAccessKey = process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY;\n const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;\n const publicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/\\s*$/, \"\");\n\n const encoder = new TextEncoder();\n\n const stream = new ReadableStream({\n async start(controller) {\n const sendEvent = (data: object) => {\n try {\n controller.enqueue(\n encoder.encode(`data: ${JSON.stringify(data)}\\n\\n`)\n );\n } catch {\n // Controller may be closed if client disconnected\n }\n };\n\n try {\n if (\n !accountId ||\n !accessKeyId ||\n !secretAccessKey ||\n !bucketName ||\n !publicUrl\n ) {\n sendEvent({\n type: \"error\",\n message:\n \"R2 not configured. Set CLOUDFLARE_R2_* environment variables.\",\n });\n controller.close();\n return;\n }\n\n const { imageKeys, operationId } = (await request.json()) as {\n imageKeys: string[];\n operationId?: string;\n };\n\n if (!imageKeys || !Array.isArray(imageKeys) || imageKeys.length === 0) {\n sendEvent({ type: \"error\", message: \"No image keys provided\" });\n controller.close();\n return;\n }\n\n // Helper to check if operation was cancelled\n const isCancelled = () =>\n operationId ? isOperationCancelled(operationId) : false;\n\n const meta = await loadMeta();\n const cdnUrls = getCdnUrls(meta);\n const cdnIndex = getOrAddCdnIndex(meta, publicUrl);\n\n const r2 = new S3Client({\n region: \"auto\",\n endpoint: `https://${accountId}.r2.cloudflarestorage.com`,\n credentials: { accessKeyId, secretAccessKey },\n });\n\n const pushed: string[] = [];\n const alreadyPushed: string[] = [];\n const errors: string[] = [];\n const sourceFolders = new Set<string>();\n const total = imageKeys.length;\n\n sendEvent({ type: \"start\", total });\n\n for (let i = 0; i < imageKeys.length; i++) {\n // Check for cancellation before each file\n if (isCancelled()) {\n await saveMeta(meta);\n // Clean up empty folders\n for (const folder of sourceFolders) {\n await deleteEmptyFolders(folder);\n }\n if (operationId) clearCancelledOperation(operationId);\n sendEvent({\n type: \"complete\",\n pushed: pushed.length,\n alreadyPushed: alreadyPushed.length,\n errors: errors.length,\n message: `Stopped. ${pushed.length} file${\n pushed.length !== 1 ? \"s\" : \"\"\n } pushed.`,\n cancelled: true,\n });\n controller.close();\n return;\n }\n\n let imageKey = imageKeys[i];\n // Normalize key to have leading /\n if (!imageKey.startsWith(\"/\")) {\n imageKey = `/${imageKey}`;\n }\n\n const entry = getMetaEntry(meta, imageKey);\n if (!entry) {\n errors.push(\n `Image not found in meta: ${imageKey}. Run Scan first.`\n );\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n pushed: pushed.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: path.basename(imageKey),\n });\n continue;\n }\n\n // Check if already pushed to our R2\n const existingCdnUrl =\n entry.c !== undefined ? cdnUrls[entry.c] : undefined;\n const isAlreadyInOurR2 = existingCdnUrl === publicUrl;\n\n if (isAlreadyInOurR2) {\n alreadyPushed.push(imageKey);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n pushed: pushed.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: path.basename(imageKey),\n });\n continue;\n }\n\n // Check if this is a remote image (in another CDN)\n const isRemote =\n entry.c !== undefined && existingCdnUrl !== publicUrl;\n\n try {\n let originalBuffer: Buffer;\n\n if (isRemote) {\n // Download from remote URL\n const remoteUrl = `${existingCdnUrl}${imageKey}`;\n originalBuffer = await downloadFromRemoteUrl(remoteUrl);\n } else {\n // Read from local file\n const originalLocalPath = getPublicPath(imageKey);\n try {\n originalBuffer = await fs.readFile(originalLocalPath);\n } catch {\n errors.push(`Original file not found: ${imageKey}`);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n pushed: pushed.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: path.basename(imageKey),\n });\n continue;\n }\n }\n\n // Upload original to R2\n await r2.send(\n new PutObjectCommand({\n Bucket: bucketName,\n Key: imageKey.replace(/^\\//, \"\"),\n Body: originalBuffer,\n ContentType: getContentType(imageKey),\n })\n );\n\n // Upload thumbnails (only if processed locally, not for remote imports)\n if (!isRemote && isProcessed(entry)) {\n for (const thumbPath of getAllThumbnailPaths(imageKey)) {\n const localPath = getPublicPath(thumbPath);\n try {\n const fileBuffer = await fs.readFile(localPath);\n await r2.send(\n new PutObjectCommand({\n Bucket: bucketName,\n Key: thumbPath.replace(/^\\//, \"\"),\n Body: fileBuffer,\n ContentType: getContentType(thumbPath),\n })\n );\n } catch {\n // Thumbnail might not exist\n }\n }\n }\n\n entry.c = cdnIndex;\n\n // Delete local files (only for non-remote, local images being pushed)\n if (!isRemote) {\n const originalLocalPath = getPublicPath(imageKey);\n\n // Track source folder for cleanup\n sourceFolders.add(path.dirname(originalLocalPath));\n\n // Delete local thumbnails\n for (const thumbPath of getAllThumbnailPaths(imageKey)) {\n const localPath = getPublicPath(thumbPath);\n sourceFolders.add(path.dirname(localPath));\n try {\n await fs.unlink(localPath);\n } catch {\n /* ignore */\n }\n }\n\n // Delete local original\n try {\n await fs.unlink(originalLocalPath);\n } catch {\n /* ignore */\n }\n }\n\n // Save meta after each successful push\n await saveMeta(meta);\n\n pushed.push(imageKey);\n } catch (error) {\n console.error(`Failed to push ${imageKey}:`, error);\n errors.push(`Failed to push: ${imageKey}`);\n }\n\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n pushed: pushed.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: path.basename(imageKey),\n });\n }\n\n // Clean up empty source folders\n for (const folder of sourceFolders) {\n await deleteEmptyFolders(folder);\n }\n\n // Build completion message\n let message: string | undefined;\n if (pushed.length === 0 && errors.length === 0) {\n message = `${alreadyPushed.length} file${\n alreadyPushed.length !== 1 ? \"s\" : \"\"\n } already on CDN. 0 new files pushed.`;\n } else if (alreadyPushed.length > 0 && errors.length === 0) {\n message = `${pushed.length} file${\n pushed.length !== 1 ? \"s\" : \"\"\n } pushed. ${alreadyPushed.length} already on CDN.`;\n }\n\n if (operationId) clearCancelledOperation(operationId);\n sendEvent({\n type: \"complete\",\n pushed: pushed.length,\n alreadyPushed: alreadyPushed.length,\n errors: errors.length,\n errorMessages: errors.length > 0 ? errors : undefined,\n message,\n });\n } catch (error) {\n console.error(\"Failed to push:\", error);\n sendEvent({ type: \"error\", message: \"Failed to push to CDN\" });\n } finally {\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n}\n\nexport async function handleReprocess(request: Request) {\n const publicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/\\s*$/, \"\");\n\n try {\n const { imageKeys } = (await request.json()) as { imageKeys: string[] };\n\n if (!imageKeys || !Array.isArray(imageKeys) || imageKeys.length === 0) {\n return jsonResponse({ error: \"No image keys provided\" }, { status: 400 });\n }\n\n const meta = await loadMeta();\n const cdnUrls = getCdnUrls(meta);\n const processed: string[] = [];\n const errors: string[] = [];\n\n for (let imageKey of imageKeys) {\n // Normalize key to have leading /\n if (!imageKey.startsWith(\"/\")) {\n imageKey = `/${imageKey}`;\n }\n\n try {\n let buffer: Buffer;\n const entry = getMetaEntry(meta, imageKey);\n const existingCdnIndex = entry?.c;\n const existingCdnUrl =\n existingCdnIndex !== undefined\n ? cdnUrls[existingCdnIndex]\n : undefined;\n\n // Determine if this is our R2 or a remote CDN\n const isInOurR2 = existingCdnUrl === publicUrl;\n const isRemote = existingCdnIndex !== undefined && !isInOurR2;\n\n const originalPath = getPublicPath(imageKey);\n\n try {\n buffer = await fs.readFile(originalPath);\n } catch {\n if (isInOurR2) {\n // Download original from our R2\n buffer = await downloadFromCdn(imageKey);\n const dir = path.dirname(originalPath);\n await fs.mkdir(dir, { recursive: true });\n await fs.writeFile(originalPath, buffer);\n } else if (isRemote && existingCdnUrl) {\n // Download from remote URL\n const remoteUrl = `${existingCdnUrl}${imageKey}`;\n buffer = await downloadFromRemoteUrl(remoteUrl);\n const dir = path.dirname(originalPath);\n await fs.mkdir(dir, { recursive: true });\n await fs.writeFile(originalPath, buffer);\n } else {\n throw new Error(`File not found: ${imageKey}`);\n }\n }\n\n const updatedEntry = await processImage(buffer, imageKey);\n // No need to set p flag - presence of thumbnail dims (sm/md/lg/f) indicates processed\n\n if (isInOurR2) {\n // Re-upload to R2 and clean up local files\n updatedEntry.c = existingCdnIndex;\n // Delete original and thumbnails from CDN first to clear cache\n await deleteOriginalFromCdn(imageKey);\n await deleteThumbnailsFromCdn(imageKey);\n // Re-upload original and thumbnails\n await uploadOriginalToCdn(imageKey);\n await uploadToCdn(imageKey);\n\n await deleteLocalThumbnails(imageKey);\n // Delete local original\n try {\n await fs.unlink(originalPath);\n } catch {\n /* ignore */\n }\n } else if (isRemote) {\n // Remote image processed locally - remove c flag, now it's local\n // Keep the original and thumbnails locally\n }\n\n meta[imageKey] = updatedEntry;\n processed.push(imageKey);\n } catch (error) {\n console.error(`Failed to reprocess ${imageKey}:`, error);\n errors.push(imageKey);\n }\n }\n\n await saveMeta(meta);\n\n return jsonResponse({\n success: true,\n processed,\n errors: errors.length > 0 ? errors : undefined,\n });\n } catch (error) {\n console.error(\"Failed to reprocess:\", error);\n return jsonResponse(\n { error: \"Failed to reprocess images\" },\n { status: 500 }\n );\n }\n}\n\nexport async function handleUnprocessStream(request: Request) {\n const publicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/\\s*$/, \"\");\n const encoder = new TextEncoder();\n\n // Parse the request body before creating the stream\n let imageKeys: string[];\n let operationId: string | undefined;\n try {\n const body = (await request.json()) as {\n imageKeys: string[];\n operationId?: string;\n };\n imageKeys = body.imageKeys;\n operationId = body.operationId;\n\n if (!imageKeys || !Array.isArray(imageKeys) || imageKeys.length === 0) {\n return jsonResponse({ error: \"No image keys provided\" }, { status: 400 });\n }\n } catch {\n return jsonResponse({ error: \"Invalid request body\" }, { status: 400 });\n }\n\n // Helper to check if operation was cancelled\n const isCancelled = () =>\n operationId ? isOperationCancelled(operationId) : false;\n\n const stream = new ReadableStream({\n async start(controller) {\n const sendEvent = (data: object) => {\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\\n\\n`));\n };\n\n try {\n const meta = await loadMeta();\n const cdnUrls = getCdnUrls(meta);\n const removed: string[] = [];\n const skipped: string[] = [];\n const errors: string[] = [];\n\n const total = imageKeys.length;\n sendEvent({ type: \"start\", total });\n\n for (let i = 0; i < imageKeys.length; i++) {\n // Check for cancellation before each image\n if (isCancelled()) {\n await saveMeta(meta);\n if (operationId) clearCancelledOperation(operationId);\n sendEvent({\n type: \"complete\",\n processed: removed.length,\n errors: errors.length,\n message: `Stopped. Removed thumbnails for ${\n removed.length\n } image${removed.length !== 1 ? \"s\" : \"\"}.`,\n cancelled: true,\n });\n controller.close();\n return;\n }\n\n let imageKey = imageKeys[i];\n\n // Normalize key to have leading /\n if (!imageKey.startsWith(\"/\")) {\n imageKey = `/${imageKey}`;\n }\n\n try {\n const entry = getMetaEntry(meta, imageKey);\n if (!entry) {\n errors.push(imageKey);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n processed: removed.length,\n percent: Math.round(((i + 1) / total) * 100),\n message: `Error: ${imageKey.slice(1)}`,\n });\n continue;\n }\n\n // Check if this image has any thumbnails\n const hasThumbnails = entry.sm || entry.md || entry.lg || entry.f;\n if (!hasThumbnails) {\n skipped.push(imageKey);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n processed: removed.length,\n percent: Math.round(((i + 1) / total) * 100),\n message: `Skipped ${imageKey.slice(1)} (no thumbnails)`,\n });\n continue;\n }\n\n const existingCdnIndex = entry.c;\n const existingCdnUrl =\n existingCdnIndex !== undefined\n ? cdnUrls[existingCdnIndex]\n : undefined;\n const isInOurR2 = existingCdnUrl === publicUrl;\n\n // Delete local thumbnails\n await deleteLocalThumbnails(imageKey);\n\n // Delete cloud thumbnails if in our R2\n if (isInOurR2) {\n await deleteThumbnailsFromCdn(imageKey);\n }\n\n // Update meta - keep o, b, c but remove thumbnail dimensions\n meta[imageKey] = {\n o: entry.o,\n b: entry.b,\n ...(entry.c !== undefined ? { c: entry.c } : {}),\n };\n\n // Save meta after each successful removal\n await saveMeta(meta);\n\n removed.push(imageKey);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n processed: removed.length,\n percent: Math.round(((i + 1) / total) * 100),\n message: `Removed thumbnails for ${imageKey.slice(1)}`,\n });\n } catch (error) {\n console.error(`Failed to unprocess ${imageKey}:`, error);\n errors.push(imageKey);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n processed: removed.length,\n percent: Math.round(((i + 1) / total) * 100),\n message: `Failed: ${imageKey.slice(1)}`,\n });\n }\n }\n\n sendEvent({ type: \"cleanup\", message: \"Saving metadata...\" });\n await saveMeta(meta);\n\n // Clean up empty folders in the images directory\n sendEvent({ type: \"cleanup\", message: \"Cleaning up empty folders...\" });\n\n const imagesDir = getPublicPath(\"images\");\n try {\n await cleanupEmptyFoldersRecursive(imagesDir);\n } catch {\n // images dir might not exist\n }\n\n // Build completion message\n let message = `Removed thumbnails from ${removed.length} image${\n removed.length !== 1 ? \"s\" : \"\"\n }.`;\n if (skipped.length > 0) {\n message += ` ${skipped.length} image${\n skipped.length !== 1 ? \"s\" : \"\"\n } had no thumbnails.`;\n }\n if (errors.length > 0) {\n message += ` ${errors.length} image${\n errors.length !== 1 ? \"s\" : \"\"\n } failed.`;\n }\n\n sendEvent({\n type: \"complete\",\n processed: removed.length,\n skipped: skipped.length,\n errors: errors.length,\n message,\n });\n\n controller.close();\n } catch (error) {\n console.error(\"Unprocess stream error:\", error);\n sendEvent({ type: \"error\", message: \"Failed to remove thumbnails\" });\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n}\n\nexport async function handleReprocessStream(request: Request) {\n const publicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/\\s*$/, \"\");\n const encoder = new TextEncoder();\n\n // Parse the request body before creating the stream\n let imageKeys: string[];\n let operationId: string | undefined;\n try {\n const body = (await request.json()) as {\n imageKeys: string[];\n operationId?: string;\n };\n imageKeys = body.imageKeys;\n operationId = body.operationId;\n\n if (!imageKeys || !Array.isArray(imageKeys) || imageKeys.length === 0) {\n return jsonResponse({ error: \"No image keys provided\" }, { status: 400 });\n }\n } catch {\n return jsonResponse({ error: \"Invalid request body\" }, { status: 400 });\n }\n\n // Helper to check if operation was cancelled\n const isCancelled = () =>\n operationId ? isOperationCancelled(operationId) : false;\n\n const stream = new ReadableStream({\n async start(controller) {\n const sendEvent = (data: object) => {\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\\n\\n`));\n };\n\n try {\n const meta = await loadMeta();\n const cdnUrls = getCdnUrls(meta);\n const processed: string[] = [];\n const errors: string[] = [];\n\n const total = imageKeys.length;\n sendEvent({ type: \"start\", total });\n\n for (let i = 0; i < imageKeys.length; i++) {\n // Check for cancellation before each image\n if (isCancelled()) {\n await saveMeta(meta);\n if (operationId) clearCancelledOperation(operationId);\n sendEvent({\n type: \"complete\",\n processed: processed.length,\n errors: errors.length,\n message: `Stopped. Generated thumbnails for ${\n processed.length\n } image${processed.length !== 1 ? \"s\" : \"\"}.`,\n cancelled: true,\n });\n controller.close();\n return;\n }\n\n let imageKey = imageKeys[i];\n\n // Normalize key to have leading /\n if (!imageKey.startsWith(\"/\")) {\n imageKey = `/${imageKey}`;\n }\n\n try {\n let buffer: Buffer;\n const entry = getMetaEntry(meta, imageKey);\n const existingCdnIndex = entry?.c;\n const existingCdnUrl =\n existingCdnIndex !== undefined\n ? cdnUrls[existingCdnIndex]\n : undefined;\n\n // Determine if this is our R2 or a remote CDN\n const isInOurR2 = existingCdnUrl === publicUrl;\n const isRemote = existingCdnIndex !== undefined && !isInOurR2;\n\n const originalPath = getPublicPath(imageKey);\n\n try {\n buffer = await fs.readFile(originalPath);\n } catch {\n if (isInOurR2) {\n buffer = await downloadFromCdn(imageKey);\n const dir = path.dirname(originalPath);\n await fs.mkdir(dir, { recursive: true });\n await fs.writeFile(originalPath, buffer);\n } else if (isRemote && existingCdnUrl) {\n const remoteUrl = `${existingCdnUrl}${imageKey}`;\n buffer = await downloadFromRemoteUrl(remoteUrl);\n const dir = path.dirname(originalPath);\n await fs.mkdir(dir, { recursive: true });\n await fs.writeFile(originalPath, buffer);\n } else {\n throw new Error(`File not found: ${imageKey}`);\n }\n }\n\n const ext = path.extname(imageKey).toLowerCase();\n const isSvg = ext === \".svg\";\n\n if (isSvg) {\n const imageDir = path.dirname(imageKey.slice(1));\n const imagesPath = getPublicPath(\n \"images\",\n imageDir === \".\" ? \"\" : imageDir\n );\n await fs.mkdir(imagesPath, { recursive: true });\n\n const fileName = path.basename(imageKey);\n const destPath = path.join(imagesPath, fileName);\n await fs.writeFile(destPath, buffer);\n\n meta[imageKey] = {\n ...entry,\n o: { w: 0, h: 0 },\n b: \"\",\n f: { w: 0, h: 0 },\n };\n\n if (isRemote) {\n delete (meta[imageKey] as import(\"../types\").MetaEntry).c;\n }\n } else {\n const updatedEntry = await processImage(buffer, imageKey);\n\n if (isInOurR2) {\n updatedEntry.c = existingCdnIndex;\n // Delete original and thumbnails from CDN first to clear cache\n await deleteOriginalFromCdn(imageKey);\n await deleteThumbnailsFromCdn(imageKey);\n // Re-upload original and thumbnails\n await uploadOriginalToCdn(imageKey);\n await uploadToCdn(imageKey);\n\n await deleteLocalThumbnails(imageKey);\n try {\n await fs.unlink(originalPath);\n } catch {\n /* ignore */\n }\n }\n\n meta[imageKey] = updatedEntry;\n }\n\n // Save meta after each successful process\n await saveMeta(meta);\n\n processed.push(imageKey);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n processed: processed.length,\n percent: Math.round(((i + 1) / total) * 100),\n message: `Processed ${imageKey.slice(1)}`,\n });\n } catch (error) {\n console.error(`Failed to reprocess ${imageKey}:`, error);\n errors.push(imageKey);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n processed: processed.length,\n percent: Math.round(((i + 1) / total) * 100),\n message: `Failed: ${imageKey.slice(1)}`,\n });\n }\n }\n\n sendEvent({ type: \"cleanup\", message: \"Saving metadata...\" });\n await saveMeta(meta);\n\n // Build completion message\n let message = `Generated thumbnails for ${processed.length} image${\n processed.length !== 1 ? \"s\" : \"\"\n }.`;\n if (errors.length > 0) {\n message += ` ${errors.length} image${\n errors.length !== 1 ? \"s\" : \"\"\n } failed.`;\n }\n\n sendEvent({\n type: \"complete\",\n processed: processed.length,\n errors: errors.length,\n message,\n });\n\n controller.close();\n } catch (error) {\n console.error(\"Reprocess stream error:\", error);\n sendEvent({ type: \"error\", message: \"Failed to generate thumbnails\" });\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n}\n\nexport async function handleProcessAllStream() {\n const publicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/\\s*$/, \"\");\n const encoder = new TextEncoder();\n\n const stream = new ReadableStream({\n async start(controller) {\n const sendEvent = (data: object) => {\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\\n\\n`));\n };\n\n try {\n const meta = await loadMeta();\n const cdnUrls = getCdnUrls(meta);\n const processed: string[] = [];\n const errors: string[] = [];\n const orphansRemoved: string[] = [];\n\n // Count images in different states\n let alreadyProcessed = 0;\n\n // Get all images from meta that need processing (no p flag = not processed yet)\n const imagesToProcess: Array<{\n key: string;\n entry: import(\"../types\").MetaEntry;\n }> = [];\n\n for (const [key, entry] of getFileEntries(meta)) {\n const fileName = path.basename(key);\n if (!isImageFile(fileName)) continue;\n\n // Check if needs processing (no thumbnail dims = not processed yet)\n if (!isProcessed(entry)) {\n imagesToProcess.push({ key, entry });\n } else {\n alreadyProcessed++;\n }\n }\n\n const total = imagesToProcess.length;\n sendEvent({ type: \"start\", total });\n\n for (let i = 0; i < imagesToProcess.length; i++) {\n const { key, entry } = imagesToProcess[i];\n const fullPath = getPublicPath(key);\n const existingCdnIndex = entry.c;\n const existingCdnUrl =\n existingCdnIndex !== undefined\n ? cdnUrls[existingCdnIndex]\n : undefined;\n\n // Determine if this is our R2 or a remote CDN\n const isInOurR2 = existingCdnUrl === publicUrl;\n const isRemote = existingCdnIndex !== undefined && !isInOurR2;\n\n try {\n let buffer: Buffer;\n\n // Download from appropriate source\n if (isInOurR2) {\n buffer = await downloadFromCdn(key);\n const dir = path.dirname(fullPath);\n await fs.mkdir(dir, { recursive: true });\n await fs.writeFile(fullPath, buffer);\n } else if (isRemote && existingCdnUrl) {\n const remoteUrl = `${existingCdnUrl}${key}`;\n buffer = await downloadFromRemoteUrl(remoteUrl);\n const dir = path.dirname(fullPath);\n await fs.mkdir(dir, { recursive: true });\n await fs.writeFile(fullPath, buffer);\n } else {\n buffer = await fs.readFile(fullPath);\n }\n\n const ext = path.extname(key).toLowerCase();\n const isSvg = ext === \".svg\";\n\n if (isSvg) {\n const imageDir = path.dirname(key.slice(1));\n const imagesPath = getPublicPath(\n \"images\",\n imageDir === \".\" ? \"\" : imageDir\n );\n await fs.mkdir(imagesPath, { recursive: true });\n\n const fileName = path.basename(key);\n const destPath = path.join(imagesPath, fileName);\n await fs.writeFile(destPath, buffer);\n\n meta[key] = {\n ...entry,\n o: { w: 0, h: 0 },\n b: \"\",\n f: { w: 0, h: 0 }, // SVG has \"full\" to indicate processed\n };\n\n // Remote images become local after processing\n if (isRemote) {\n delete (meta[key] as import(\"../types\").MetaEntry).c;\n }\n } else {\n const processedEntry = await processImage(buffer, key);\n meta[key] = {\n ...processedEntry,\n ...(isInOurR2 ? { c: existingCdnIndex } : {}),\n };\n // Remote images become local after processing (no c)\n }\n\n // If image was in our R2, re-upload original + thumbnails and clean up local files\n if (isInOurR2) {\n // Delete original and thumbnails from CDN first to clear cache\n await deleteOriginalFromCdn(key);\n await deleteThumbnailsFromCdn(key);\n // Re-upload original and thumbnails\n await uploadOriginalToCdn(key);\n await uploadToCdn(key);\n\n await deleteLocalThumbnails(key);\n // Delete local original\n try {\n await fs.unlink(fullPath);\n } catch {\n /* ignore */\n }\n }\n // Remote images stay local after processing (original + thumbnails)\n\n processed.push(key.slice(1));\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n processed: processed.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: key.slice(1),\n });\n } catch (error) {\n console.error(`Failed to process ${key}:`, error);\n errors.push(key.slice(1));\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n processed: processed.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: key.slice(1),\n message: `Failed: ${key.slice(1)}`,\n });\n }\n }\n\n sendEvent({\n type: \"cleanup\",\n message: \"Removing orphaned thumbnails...\",\n });\n\n // Build set of expected thumbnail paths\n const trackedPaths = new Set<string>();\n for (const [imageKey, entry] of getFileEntries(meta)) {\n // Only track local thumbnails (not pushed to CDN)\n if (entry.c === undefined) {\n for (const thumbPath of getAllThumbnailPaths(imageKey)) {\n trackedPaths.add(thumbPath);\n }\n }\n }\n\n async function findOrphans(\n dir: string,\n relativePath: string = \"\"\n ): Promise<void> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n\n for (const fsEntry of entries) {\n if (fsEntry.name.startsWith(\".\")) continue;\n\n const entryFullPath = path.join(dir, fsEntry.name);\n const relPath = relativePath\n ? `${relativePath}/${fsEntry.name}`\n : fsEntry.name;\n\n if (fsEntry.isDirectory()) {\n await findOrphans(entryFullPath, relPath);\n } else if (isImageFile(fsEntry.name)) {\n const publicPath = `/images/${relPath}`;\n if (!trackedPaths.has(publicPath)) {\n try {\n await fs.unlink(entryFullPath);\n orphansRemoved.push(publicPath);\n } catch (err) {\n console.error(\n `Failed to remove orphan ${publicPath}:`,\n err\n );\n }\n }\n }\n }\n } catch {\n // Directory might not exist\n }\n }\n\n const imagesDir = getPublicPath(\"images\");\n try {\n await findOrphans(imagesDir);\n } catch {\n // images dir might not exist\n }\n\n async function removeEmptyDirs(dir: string): Promise<boolean> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n let isEmpty = true;\n\n for (const fsEntry of entries) {\n if (fsEntry.isDirectory()) {\n const subDirEmpty = await removeEmptyDirs(\n path.join(dir, fsEntry.name)\n );\n if (!subDirEmpty) isEmpty = false;\n } else {\n isEmpty = false;\n }\n }\n\n if (isEmpty && dir !== imagesDir) {\n await fs.rmdir(dir);\n }\n\n return isEmpty;\n } catch {\n return true;\n }\n }\n\n try {\n await removeEmptyDirs(imagesDir);\n } catch {\n // images dir might not exist\n }\n\n await saveMeta(meta);\n\n sendEvent({\n type: \"complete\",\n processed: processed.length,\n alreadyProcessed,\n orphansRemoved: orphansRemoved.length,\n errors: errors.length,\n });\n } catch (error) {\n console.error(\"Failed to process all:\", error);\n sendEvent({ type: \"error\", message: \"Failed to process images\" });\n } finally {\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n}\n\n/**\n * Download images from R2 CDN to local storage (streaming)\n * This removes the images from R2 and stores them locally\n */\nexport async function handleDownloadStream(request: Request) {\n const { imageKeys, operationId } = (await request.json()) as {\n imageKeys: string[];\n operationId?: string;\n };\n\n if (!imageKeys || !Array.isArray(imageKeys) || imageKeys.length === 0) {\n return jsonResponse({ error: \"No image keys provided\" }, { status: 400 });\n }\n\n const stream = new ReadableStream({\n async start(controller) {\n const encoder = new TextEncoder();\n const sendEvent = (data: Record<string, unknown>) => {\n try {\n controller.enqueue(\n encoder.encode(`data: ${JSON.stringify(data)}\\n\\n`)\n );\n } catch {\n // Controller may be closed if client disconnected\n }\n };\n\n sendEvent({ type: \"start\", total: imageKeys.length });\n\n const downloaded: string[] = [];\n const skipped: string[] = [];\n const errors: string[] = [];\n\n // Helper to check if operation was cancelled\n const isCancelled = () =>\n operationId ? isOperationCancelled(operationId) : false;\n\n try {\n const meta = await loadMeta();\n\n for (let i = 0; i < imageKeys.length; i++) {\n // Check if operation was cancelled\n if (isCancelled()) {\n // Save meta with what we've done so far\n await saveMeta(meta);\n if (operationId) clearCancelledOperation(operationId);\n sendEvent({\n type: \"complete\",\n downloaded: downloaded.length,\n message: `Stopped. ${downloaded.length} image${\n downloaded.length !== 1 ? \"s\" : \"\"\n } downloaded.`,\n cancelled: true,\n });\n controller.close();\n return;\n }\n\n const imageKey = imageKeys[i];\n const entry = getMetaEntry(meta, imageKey);\n\n if (!entry || entry.c === undefined) {\n skipped.push(imageKey);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total: imageKeys.length,\n downloaded: downloaded.length,\n message: `Skipped ${imageKey} (not on cloud)`,\n });\n continue;\n }\n\n try {\n // Download original from R2\n const imageBuffer = await downloadFromCdn(imageKey);\n\n // Check again after download (long operation)\n if (isCancelled()) {\n await saveMeta(meta);\n if (operationId) clearCancelledOperation(operationId);\n sendEvent({\n type: \"complete\",\n downloaded: downloaded.length,\n message: `Stopped. ${downloaded.length} image${\n downloaded.length !== 1 ? \"s\" : \"\"\n } downloaded.`,\n cancelled: true,\n });\n controller.close();\n return;\n }\n\n // Ensure directory exists\n const localPath = getPublicPath(imageKey.replace(/^\\//, \"\"));\n await fs.mkdir(path.dirname(localPath), { recursive: true });\n\n // Write to local filesystem\n await fs.writeFile(localPath, imageBuffer);\n\n // Delete original and thumbnails from R2\n await deleteOriginalFromCdn(imageKey);\n await deleteThumbnailsFromCdn(imageKey);\n\n // Check if image was processed (has thumbnails)\n const wasProcessed = isProcessed(entry);\n\n // Remove the c property (no longer on CDN)\n delete entry.c;\n\n // If it was processed, regenerate thumbnails locally\n if (wasProcessed) {\n const processedEntry = await processImage(imageBuffer, imageKey);\n // Update dimensions in meta\n entry.sm = processedEntry.sm;\n entry.md = processedEntry.md;\n entry.lg = processedEntry.lg;\n entry.f = processedEntry.f;\n }\n\n // Save meta after each successful download\n await saveMeta(meta);\n\n downloaded.push(imageKey);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total: imageKeys.length,\n downloaded: downloaded.length,\n message: `Downloaded ${imageKey}`,\n });\n } catch (error) {\n console.error(`Failed to download ${imageKey}:`, error);\n errors.push(imageKey);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total: imageKeys.length,\n downloaded: downloaded.length,\n message: `Failed to download ${imageKey}`,\n });\n }\n }\n\n await saveMeta(meta);\n\n // Build completion message\n let message = `Downloaded ${downloaded.length} image${\n downloaded.length !== 1 ? \"s\" : \"\"\n }.`;\n if (skipped.length > 0) {\n message += ` ${skipped.length} image${\n skipped.length !== 1 ? \"s were\" : \" was\"\n } not on cloud.`;\n }\n if (errors.length > 0) {\n message += ` ${errors.length} image${\n errors.length !== 1 ? \"s\" : \"\"\n } failed.`;\n }\n\n sendEvent({\n type: \"complete\",\n downloaded: downloaded.length,\n skipped: skipped.length,\n errors: errors.length,\n message,\n });\n } catch (error) {\n console.error(\"Download stream error:\", error);\n sendEvent({ type: \"error\", message: \"Failed to download images\" });\n } finally {\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n}\n\n/**\n * Push pending updates to cloud (replace cloud files with local versions)\n * Streaming handler for progress feedback\n */\nexport async function handlePushUpdatesStream(request: Request) {\n const accountId = process.env.CLOUDFLARE_R2_ACCOUNT_ID;\n const accessKeyId = process.env.CLOUDFLARE_R2_ACCESS_KEY_ID;\n const secretAccessKey = process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY;\n const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;\n const publicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/$/, \"\");\n\n const encoder = new TextEncoder();\n\n const stream = new ReadableStream({\n async start(controller) {\n const sendEvent = (data: object) => {\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\\n\\n`));\n };\n\n try {\n if (\n !accountId ||\n !accessKeyId ||\n !secretAccessKey ||\n !bucketName ||\n !publicUrl\n ) {\n sendEvent({ type: \"error\", message: \"R2 not configured\" });\n controller.close();\n return;\n }\n\n const { paths: inputPaths, operationId } = (await request.json()) as {\n paths: string[];\n operationId?: string;\n };\n\n if (\n !inputPaths ||\n !Array.isArray(inputPaths) ||\n inputPaths.length === 0\n ) {\n sendEvent({ type: \"error\", message: \"No paths provided\" });\n controller.close();\n return;\n }\n\n // Helper to check if operation was cancelled\n const isCancelled = () =>\n operationId ? isOperationCancelled(operationId) : false;\n\n const s3 = new S3Client({\n region: \"auto\",\n endpoint: `https://${accountId}.r2.cloudflarestorage.com`,\n credentials: { accessKeyId, secretAccessKey },\n });\n\n const meta = await loadMeta();\n const cdnUrls = getCdnUrls(meta);\n const r2PublicUrl = publicUrl.replace(/\\/$/, \"\");\n\n // Expand folder paths to individual file paths with pending updates\n const paths: string[] = [];\n for (const inputPath of inputPaths) {\n const key = inputPath.startsWith(\"public/\")\n ? \"/\" + inputPath.slice(7)\n : inputPath;\n // Check if this is a folder path (no extension or ends without extension-like pattern)\n const isFolder = !key.match(/\\.[a-zA-Z0-9]+$/);\n\n if (isFolder) {\n // Find all files in meta that start with this folder path and have u: 1\n const folderPrefix = key.endsWith(\"/\") ? key : key + \"/\";\n for (const [metaKey, entry] of Object.entries(meta)) {\n if (\n metaKey.startsWith(folderPrefix) &&\n entry &&\n typeof entry === \"object\" &&\n \"u\" in entry &&\n entry.u === 1\n ) {\n paths.push(metaKey);\n }\n }\n } else {\n paths.push(inputPath);\n }\n }\n\n const pushed: string[] = [];\n const skipped: string[] = [];\n const errors: string[] = [];\n const total = paths.length;\n\n if (total === 0) {\n sendEvent({\n type: \"complete\",\n pushed: 0,\n message: \"No files with pending updates found.\",\n });\n controller.close();\n return;\n }\n\n sendEvent({ type: \"start\", total });\n\n for (let i = 0; i < paths.length; i++) {\n // Check for cancellation before each file\n if (isCancelled()) {\n await saveMeta(meta);\n if (operationId) clearCancelledOperation(operationId);\n sendEvent({\n type: \"complete\",\n pushed: pushed.length,\n message: `Stopped. ${pushed.length} file${\n pushed.length !== 1 ? \"s\" : \"\"\n } pushed.`,\n cancelled: true,\n });\n controller.close();\n return;\n }\n const itemPath = paths[i];\n const key = itemPath.startsWith(\"public/\")\n ? \"/\" + itemPath.slice(7)\n : itemPath;\n const entry = meta[key] as MetaEntry | undefined;\n\n if (!entry || entry.u !== 1) {\n skipped.push(key);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n pushed: pushed.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: path.basename(key),\n });\n continue;\n }\n\n // Check if this is an R2 file\n const fileCdnUrl =\n entry.c !== undefined\n ? cdnUrls[entry.c]?.replace(/\\/$/, \"\")\n : undefined;\n if (!fileCdnUrl || fileCdnUrl !== r2PublicUrl) {\n skipped.push(key);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n pushed: pushed.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: path.basename(key),\n });\n continue;\n }\n\n try {\n // Read the local file\n const localPath = getPublicPath(key);\n const buffer = await fs.readFile(localPath);\n const contentType = getContentType(path.basename(key));\n\n // Delete from CDN first to clear cache\n const uploadKey = key.startsWith(\"/\") ? key.slice(1) : key;\n try {\n await s3.send(\n new DeleteObjectCommand({\n Bucket: bucketName,\n Key: uploadKey,\n })\n );\n } catch {\n // Ignore delete errors - file might not exist\n }\n\n // Upload to R2\n await s3.send(\n new PutObjectCommand({\n Bucket: bucketName,\n Key: uploadKey,\n Body: buffer,\n ContentType: contentType,\n })\n );\n\n // If image is processed, also update thumbnails\n if (isProcessed(entry)) {\n // Delete existing thumbnails from CDN first\n await deleteThumbnailsFromCdn(key);\n\n // Re-process to generate new thumbnails from local file\n const processedEntry = await processImage(buffer, key);\n Object.assign(entry, processedEntry);\n\n // Upload thumbnails\n await uploadToCdn(key);\n\n // Delete local thumbnails\n await deleteLocalThumbnails(key);\n }\n\n // Delete local file (it's now on cloud)\n await fs.unlink(localPath);\n\n // Remove the update flag\n delete entry.u;\n\n // Save meta after each successful push\n await saveMeta(meta);\n\n pushed.push(key);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n pushed: pushed.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: path.basename(key),\n });\n } catch (error) {\n console.error(`Failed to push update for ${key}:`, error);\n errors.push(key);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n pushed: pushed.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: path.basename(key),\n message: `Failed: ${path.basename(key)}`,\n });\n }\n }\n\n // Clean up empty folders\n sendEvent({ type: \"cleanup\", message: \"Cleaning up...\" });\n for (const itemPath of pushed) {\n const localPath = getPublicPath(itemPath);\n await deleteEmptyFolders(path.dirname(localPath));\n }\n\n await saveMeta(meta);\n\n let message = `Pushed ${pushed.length} update${\n pushed.length !== 1 ? \"s\" : \"\"\n } to cloud.`;\n if (skipped.length > 0) {\n message += ` ${skipped.length} file${\n skipped.length !== 1 ? \"s\" : \"\"\n } skipped.`;\n }\n if (errors.length > 0) {\n message += ` ${errors.length} file${\n errors.length !== 1 ? \"s\" : \"\"\n } failed.`;\n }\n\n sendEvent({\n type: \"complete\",\n pushed: pushed.length,\n skipped: skipped.length,\n errors: errors.length,\n message,\n });\n } catch (error) {\n console.error(\"Push updates error:\", error);\n sendEvent({ type: \"error\", message: \"Failed to push updates\" });\n } finally {\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n}\n\n/**\n * Cancel a streaming operation (download, push, etc.)\n */\nexport async function handleCancelStreamOperation(request: Request) {\n try {\n const { operationId } = await request.json();\n\n if (!operationId || typeof operationId !== \"string\") {\n return jsonResponse(\n { error: \"No operation ID provided\" },\n { status: 400 }\n );\n }\n\n cancelOperation(operationId);\n\n return jsonResponse({ success: true, operationId });\n } catch (error) {\n console.error(\"Failed to cancel operation:\", error);\n return jsonResponse(\n { error: \"Failed to cancel operation\" },\n { status: 500 }\n );\n }\n}\n\n/**\n * Cancel pending updates (delete local files, keep cloud versions)\n */\nexport async function handleCancelUpdates(request: Request) {\n try {\n const { paths: inputPaths } = await request.json();\n\n if (!inputPaths || !Array.isArray(inputPaths) || inputPaths.length === 0) {\n return jsonResponse({ error: \"No paths provided\" }, { status: 400 });\n }\n\n const meta = await loadMeta();\n\n // Expand folder paths to individual file paths with pending updates\n const paths: string[] = [];\n for (const inputPath of inputPaths) {\n const key = inputPath.startsWith(\"public/\")\n ? \"/\" + inputPath.slice(7)\n : inputPath;\n // Check if this is a folder path (no extension or ends without extension-like pattern)\n const isFolder = !key.match(/\\.[a-zA-Z0-9]+$/);\n\n if (isFolder) {\n // Find all files in meta that start with this folder path and have u: 1\n const folderPrefix = key.endsWith(\"/\") ? key : key + \"/\";\n for (const [metaKey, entry] of Object.entries(meta)) {\n if (\n metaKey.startsWith(folderPrefix) &&\n entry &&\n typeof entry === \"object\" &&\n \"u\" in entry &&\n entry.u === 1\n ) {\n paths.push(metaKey);\n }\n }\n } else {\n paths.push(inputPath);\n }\n }\n\n const cancelled: string[] = [];\n const skipped: string[] = [];\n const errors: string[] = [];\n const foldersToClean = new Set<string>();\n\n for (const itemPath of paths) {\n const key = itemPath.startsWith(\"public/\")\n ? \"/\" + itemPath.slice(7)\n : itemPath;\n const entry = meta[key] as { u?: 1 } | undefined;\n\n if (!entry || entry.u !== 1) {\n skipped.push(key);\n continue;\n }\n\n try {\n // Delete the local file\n const localPath = getPublicPath(key);\n await fs.unlink(localPath);\n\n // Track folder for cleanup\n foldersToClean.add(path.dirname(localPath));\n\n // Remove the update flag\n delete entry.u;\n\n cancelled.push(key);\n } catch (error) {\n console.error(`Failed to cancel update for ${key}:`, error);\n errors.push(key);\n }\n }\n\n // Clean up empty folders\n for (const folder of foldersToClean) {\n await deleteEmptyFolders(folder);\n }\n\n await saveMeta(meta);\n\n return jsonResponse({\n success: true,\n cancelled: cancelled.length,\n skipped: skipped.length,\n errors: errors.length,\n });\n } catch (error) {\n console.error(\"Cancel updates error:\", error);\n return jsonResponse({ error: \"Failed to cancel updates\" }, { status: 500 });\n }\n}\n","import { promises as fs } from \"fs\";\nimport path from \"path\";\nimport sharp from \"sharp\";\nimport { jsonResponse } from \"./utils/response\";\nimport {\n loadMeta,\n saveMeta,\n isMediaFile,\n isImageFile,\n getFileEntries,\n slugifyFilename,\n} from \"./utils\";\nimport { getAllThumbnailPaths, isProcessed } from \"../types\";\nimport { getPublicPath } from \"../config\";\nimport {\n deleteEmptyFolders,\n cleanupEmptyFoldersRecursive,\n} from \"./utils/folders\";\n\n/**\n * Streaming scan handler - scans filesystem for new files not in meta\n * For images, reads dimensions (w/h)\n * Handles collisions by renaming files with -1, -2, etc.\n * Also detects orphaned files in the images folder\n */\nexport async function handleScanStream() {\n const encoder = new TextEncoder();\n\n const stream = new ReadableStream({\n async start(controller) {\n const sendEvent = (data: object) => {\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\\n\\n`));\n };\n\n try {\n const meta = await loadMeta();\n const existingCount = Object.keys(meta).filter(\n (k) => !k.startsWith(\"_\")\n ).length;\n const existingKeys = new Set(Object.keys(meta));\n const added: string[] = [];\n const renamed: Array<{ from: string; to: string }> = [];\n const errors: string[] = [];\n const orphanedFiles: string[] = [];\n const pendingUpdates: string[] = []; // Files that override cloud files\n\n // Collect all files first\n const allFiles: Array<{ relativePath: string; fullPath: string }> = [];\n\n async function scanDir(\n dir: string,\n relativePath: string = \"\"\n ): Promise<void> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.name.startsWith(\".\")) continue;\n\n const fullPath = path.join(dir, entry.name);\n const relPath = relativePath\n ? `${relativePath}/${entry.name}`\n : entry.name;\n\n // Skip the images folder (generated thumbnails)\n if (relPath === \"images\" || relPath.startsWith(\"images/\"))\n continue;\n\n if (entry.isDirectory()) {\n await scanDir(fullPath, relPath);\n } else if (isMediaFile(entry.name)) {\n allFiles.push({ relativePath: relPath, fullPath });\n }\n }\n } catch {\n // Directory might not exist\n }\n }\n\n const publicDir = getPublicPath();\n await scanDir(publicDir);\n\n const total = allFiles.length;\n sendEvent({ type: \"start\", total });\n\n for (let i = 0; i < allFiles.length; i++) {\n let { relativePath, fullPath } = allFiles[i];\n let imageKey = \"/\" + relativePath;\n\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: relativePath,\n });\n\n // Check if already in meta\n if (existingKeys.has(imageKey)) {\n // Check if this is a cloud file with a local override\n const entry = meta[imageKey] as { c?: number; u?: 1 } | undefined;\n if (entry?.c !== undefined && !entry?.u) {\n // This is a cloud file - local file is an override\n // Mark as pending update\n entry.u = 1;\n pendingUpdates.push(imageKey);\n }\n // File already tracked - skip adding\n continue;\n }\n\n // Slugify filename to be URL-safe (lowercase, no spaces, etc.)\n const dirName = path.dirname(relativePath);\n const originalFileName = path.basename(relativePath);\n const sluggedFileName = slugifyFilename(originalFileName);\n\n // Check if filename needs to be slugified\n if (sluggedFileName !== originalFileName) {\n const newRelativePath =\n dirName === \".\"\n ? sluggedFileName\n : `${dirName}/${sluggedFileName}`;\n const newFullPath = getPublicPath(newRelativePath);\n const newKey = \"/\" + newRelativePath;\n\n // Check if slugged name already exists\n if (!meta[newKey] && !existingKeys.has(newKey)) {\n try {\n await fs.mkdir(path.dirname(newFullPath), { recursive: true });\n await fs.rename(fullPath, newFullPath);\n renamed.push({ from: relativePath, to: newRelativePath });\n relativePath = newRelativePath;\n fullPath = newFullPath;\n imageKey = newKey;\n } catch (err) {\n console.error(`Failed to slugify ${relativePath}:`, err);\n // Continue with original name if rename fails\n }\n }\n }\n\n // Check for collision (path exists in meta but file is new)\n if (meta[imageKey]) {\n // Need to rename this file to avoid collision\n const ext = path.extname(relativePath);\n const baseName = relativePath.slice(0, -ext.length);\n let counter = 1;\n let newKey = `/${baseName}-${counter}${ext}`;\n\n while (meta[newKey]) {\n counter++;\n newKey = `/${baseName}-${counter}${ext}`;\n }\n\n // Rename the physical file\n const newRelativePath = `${baseName}-${counter}${ext}`;\n const newFullPath = getPublicPath(newRelativePath);\n\n try {\n await fs.rename(fullPath, newFullPath);\n renamed.push({ from: relativePath, to: newRelativePath });\n relativePath = newRelativePath;\n fullPath = newFullPath;\n imageKey = newKey;\n } catch (err) {\n console.error(`Failed to rename ${relativePath}:`, err);\n errors.push(`Failed to rename ${relativePath}`);\n continue;\n }\n }\n\n try {\n const isImage = isImageFile(relativePath);\n\n if (isImage) {\n // Read dimensions for images\n const ext = path.extname(relativePath).toLowerCase();\n\n if (ext === \".svg\") {\n // SVGs don't have pixel dimensions in the same way\n meta[imageKey] = { o: { w: 0, h: 0 } };\n } else {\n try {\n const buffer = await fs.readFile(fullPath);\n // Apply EXIF rotation to get correct dimensions\n const rotatedBuffer = await sharp(buffer).rotate().toBuffer();\n const metadata = await sharp(rotatedBuffer).metadata();\n\n meta[imageKey] = {\n o: { w: metadata.width || 0, h: metadata.height || 0 },\n };\n } catch {\n // Couldn't read dimensions\n meta[imageKey] = { o: { w: 0, h: 0 } };\n }\n }\n } else {\n // Non-image files - just add empty entry\n meta[imageKey] = {};\n }\n\n existingKeys.add(imageKey);\n added.push(imageKey);\n\n // Save meta periodically (every 10 files) to avoid losing progress on crash\n if (added.length % 10 === 0) {\n await saveMeta(meta);\n }\n } catch (error) {\n console.error(`Failed to process ${relativePath}:`, error);\n errors.push(relativePath);\n }\n }\n\n // Check for orphaned files in the images folder\n sendEvent({\n type: \"cleanup\",\n message: \"Checking for orphaned thumbnails...\",\n });\n\n // Build set of expected thumbnail paths from meta entries\n const expectedThumbnails = new Set<string>();\n const fileEntries = getFileEntries(meta);\n for (const [imageKey, entry] of fileEntries) {\n // Only track local thumbnails (not pushed to CDN)\n if (entry.c === undefined && isProcessed(entry)) {\n for (const thumbPath of getAllThumbnailPaths(imageKey)) {\n expectedThumbnails.add(thumbPath);\n }\n }\n }\n\n // Scan the images folder for orphaned files\n async function findOrphans(\n dir: string,\n relativePath: string = \"\"\n ): Promise<void> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.name.startsWith(\".\")) continue;\n\n const fullPath = path.join(dir, entry.name);\n const relPath = relativePath\n ? `${relativePath}/${entry.name}`\n : entry.name;\n\n if (entry.isDirectory()) {\n await findOrphans(fullPath, relPath);\n } else if (isImageFile(entry.name)) {\n const publicPath = `/images/${relPath}`;\n if (!expectedThumbnails.has(publicPath)) {\n orphanedFiles.push(publicPath);\n }\n }\n }\n } catch {\n // Directory might not exist\n }\n }\n\n const imagesDir = getPublicPath(\"images\");\n try {\n await findOrphans(imagesDir);\n } catch {\n // images dir might not exist\n }\n\n // Clean up empty folders in the public directory\n sendEvent({ type: \"cleanup\", message: \"Cleaning up empty folders...\" });\n let emptyFoldersDeleted = 0;\n\n async function cleanEmptyFolders(dir: string): Promise<void> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.name.startsWith(\".\")) continue;\n if (!entry.isDirectory()) continue;\n\n // Skip the images folder (handled separately by deleteOrphans)\n const fullPath = path.join(dir, entry.name);\n if (fullPath === imagesDir) continue;\n\n // Recursively clean subdirectories first\n await cleanEmptyFolders(fullPath);\n\n // Then try to delete this folder if empty\n try {\n const subEntries = await fs.readdir(fullPath);\n const meaningfulEntries = subEntries.filter(\n (e) => !e.startsWith(\".\")\n );\n if (meaningfulEntries.length === 0) {\n await fs.rm(fullPath, { recursive: true });\n emptyFoldersDeleted++;\n }\n } catch {\n // Folder might already be deleted or not accessible\n }\n }\n } catch {\n // Directory might not exist or not readable\n }\n }\n\n await cleanEmptyFolders(getPublicPath());\n\n // Also clean up empty folders inside the images directory\n async function cleanImagesEmptyFolders(dir: string): Promise<boolean> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n let isEmpty = true;\n\n for (const entry of entries) {\n if (entry.isDirectory()) {\n const subDirEmpty = await cleanImagesEmptyFolders(\n path.join(dir, entry.name)\n );\n if (!subDirEmpty) isEmpty = false;\n } else if (!entry.name.startsWith(\".\")) {\n isEmpty = false;\n }\n }\n\n if (isEmpty && dir !== imagesDir) {\n await fs.rm(dir, { recursive: true });\n emptyFoldersDeleted++;\n }\n\n return isEmpty;\n } catch {\n return true;\n }\n }\n\n try {\n await cleanImagesEmptyFolders(imagesDir);\n } catch {\n // images dir might not exist\n }\n\n // Clean up orphaned meta entries (local files that no longer exist)\n sendEvent({\n type: \"cleanup\",\n message: \"Checking for orphaned entries...\",\n });\n const orphanedEntries: string[] = [];\n const cdnUrls = (meta._cdns || []) as string[];\n const r2PublicUrl = (\n process.env.CLOUDFLARE_R2_PUBLIC_URL || \"\"\n ).replace(/\\/$/, \"\");\n\n for (const key of Object.keys(meta)) {\n if (key.startsWith(\"_\")) continue; // Skip special keys\n\n const entry = meta[key] as { c?: number; u?: 1 } | undefined;\n if (!entry) continue;\n\n // Skip cloud files - they still exist in the cloud\n if (entry.c !== undefined) {\n // But if it has u:1 flag, the local override might have been deleted\n if (entry.u === 1) {\n const localPath = getPublicPath(key);\n try {\n await fs.access(localPath);\n } catch {\n // Local override was deleted, remove the u flag\n delete entry.u;\n }\n }\n continue;\n }\n\n // For local files, check if they still exist\n const localPath = getPublicPath(key);\n try {\n await fs.access(localPath);\n } catch {\n // File doesn't exist - orphaned entry\n orphanedEntries.push(key);\n delete meta[key];\n }\n }\n\n if (orphanedEntries.length > 0) {\n sendEvent({\n type: \"cleanup\",\n message: `Removed ${orphanedEntries.length} orphaned entries...`,\n });\n }\n\n await saveMeta(meta);\n\n sendEvent({\n type: \"complete\",\n existingCount,\n added: added.length,\n renamed: renamed.length,\n errors: errors.length,\n renamedFiles: renamed,\n orphanedFiles: orphanedFiles.length > 0 ? orphanedFiles : undefined,\n pendingUpdates: pendingUpdates.length,\n orphanedEntries: orphanedEntries.length,\n emptyFoldersDeleted:\n emptyFoldersDeleted > 0 ? emptyFoldersDeleted : undefined,\n });\n } catch (error) {\n console.error(\"Scan failed:\", error);\n sendEvent({ type: \"error\", message: \"Scan failed\" });\n } finally {\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n}\n\n/**\n * Delete orphaned files from the images folder\n */\nexport async function handleDeleteOrphans(request: Request) {\n try {\n const { paths } = (await request.json()) as { paths: string[] };\n\n if (!paths || !Array.isArray(paths) || paths.length === 0) {\n return jsonResponse({ error: \"No paths provided\" }, { status: 400 });\n }\n\n const deleted: string[] = [];\n const errors: string[] = [];\n\n for (const orphanPath of paths) {\n // Ensure the path is within the images folder for safety\n if (!orphanPath.startsWith(\"/images/\")) {\n errors.push(`Invalid path: ${orphanPath}`);\n continue;\n }\n\n const fullPath = getPublicPath(orphanPath);\n\n try {\n await fs.unlink(fullPath);\n deleted.push(orphanPath);\n } catch (err) {\n console.error(`Failed to delete ${orphanPath}:`, err);\n errors.push(orphanPath);\n }\n }\n\n // Clean up empty directories (including images folder itself)\n const imagesDir = getPublicPath(\"images\");\n\n try {\n await cleanupEmptyFoldersRecursive(imagesDir);\n } catch {\n // images dir might not exist\n }\n\n return jsonResponse({\n success: true,\n deleted: deleted.length,\n errors: errors.length,\n });\n } catch (error) {\n console.error(\"Failed to delete orphans:\", error);\n return jsonResponse(\n { error: \"Failed to delete orphaned files\" },\n { status: 500 }\n );\n }\n}\n","import sharp from \"sharp\";\nimport {\n loadMeta,\n saveMeta,\n getOrAddCdnIndex,\n getMetaEntry,\n setMetaEntry,\n} from \"./utils\";\nimport { isOperationCancelled, clearCancelledOperation } from \"./images\";\nimport type { Dimensions } from \"../types\";\n\n/**\n * Parse an image URL into base URL and path\n */\nfunction parseImageUrl(url: string): { base: string; path: string } {\n const parsed = new URL(url);\n // Base is protocol + host\n const base = `${parsed.protocol}//${parsed.host}`;\n // Path is everything after\n const path = parsed.pathname;\n return { base, path };\n}\n\n/**\n * Fetch remote image and get dimensions\n */\nasync function processRemoteImage(\n url: string\n): Promise<{ o: Dimensions; b?: string }> {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`Failed to fetch: ${response.status}`);\n }\n\n const buffer = Buffer.from(await response.arrayBuffer());\n\n // Apply EXIF rotation to get correct dimensions\n const rotatedBuffer = await sharp(buffer).rotate().toBuffer();\n const metadata = await sharp(rotatedBuffer).metadata();\n\n return {\n o: { w: metadata.width || 0, h: metadata.height || 0 },\n // b: blur hash would be generated here if needed\n };\n}\n\n/**\n * Streaming endpoint to import images from URLs\n */\nexport async function handleImportUrls(request: Request) {\n const encoder = new TextEncoder();\n\n const stream = new ReadableStream({\n async start(controller) {\n const sendEvent = (data: object) => {\n try {\n controller.enqueue(\n encoder.encode(`data: ${JSON.stringify(data)}\\n\\n`)\n );\n } catch {\n // Controller may be closed\n }\n };\n\n try {\n const { urls, operationId } = (await request.json()) as {\n urls: string[];\n operationId?: string;\n };\n\n if (!urls || !Array.isArray(urls) || urls.length === 0) {\n sendEvent({ type: \"error\", message: \"No URLs provided\" });\n controller.close();\n return;\n }\n\n // Helper to check if operation was cancelled\n const isCancelled = () =>\n operationId ? isOperationCancelled(operationId) : false;\n\n const meta = await loadMeta();\n const added: string[] = [];\n const skipped: string[] = [];\n const errors: string[] = [];\n\n const total = urls.length;\n sendEvent({ type: \"start\", total });\n\n for (let i = 0; i < urls.length; i++) {\n // Check for cancellation before each URL\n if (isCancelled()) {\n await saveMeta(meta);\n if (operationId) clearCancelledOperation(operationId);\n sendEvent({\n type: \"complete\",\n added: added.length,\n skipped: skipped.length,\n errors: errors.length,\n message: `Stopped. Imported ${added.length} URL${\n added.length !== 1 ? \"s\" : \"\"\n }.`,\n cancelled: true,\n });\n controller.close();\n return;\n }\n\n const url = urls[i].trim();\n if (!url) continue;\n\n try {\n // Parse URL to get base and path\n const { base, path } = parseImageUrl(url);\n\n // Check if this path already exists in meta\n const existingEntry = getMetaEntry(meta, path);\n if (existingEntry) {\n skipped.push(path);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n imported: added.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: url,\n });\n continue;\n }\n\n // Get or add CDN URL to _cdns array\n const cdnIndex = getOrAddCdnIndex(meta, base);\n\n // Fetch and process the image\n const imageData = await processRemoteImage(url);\n\n // Add entry to meta\n // Note: No thumbnail dims since this is an external image, not processed locally\n setMetaEntry(meta, path, {\n o: imageData.o,\n b: imageData.b,\n c: cdnIndex,\n });\n\n // Save meta incrementally after each successful import\n await saveMeta(meta);\n\n added.push(path);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n imported: added.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: url,\n });\n } catch (error) {\n console.error(`Failed to import ${url}:`, error);\n errors.push(url);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n imported: added.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: url,\n });\n }\n }\n\n await saveMeta(meta);\n if (operationId) clearCancelledOperation(operationId);\n\n sendEvent({\n type: \"complete\",\n added: added.length,\n skipped: skipped.length,\n errors: errors.length,\n });\n } catch (error) {\n console.error(\"Import failed:\", error);\n sendEvent({ type: \"error\", message: \"Import failed\" });\n } finally {\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n}\n\n/**\n * Get CDN URLs for settings\n */\nexport async function handleGetCdns() {\n try {\n const meta = await loadMeta();\n const cdns = meta._cdns || [];\n\n return Response.json({ cdns });\n } catch (error) {\n console.error(\"Failed to get CDNs:\", error);\n return Response.json({ error: \"Failed to get CDNs\" }, { status: 500 });\n }\n}\n\n/**\n * Update CDN URLs from settings\n */\nexport async function handleUpdateCdns(request: Request) {\n try {\n const { cdns } = (await request.json()) as { cdns: string[] };\n\n if (!Array.isArray(cdns)) {\n return Response.json({ error: \"Invalid CDN array\" }, { status: 400 });\n }\n\n const meta = await loadMeta();\n\n // Normalize URLs (remove trailing slashes)\n meta._cdns = cdns.map((url) => url.replace(/\\/$/, \"\"));\n\n await saveMeta(meta);\n\n return Response.json({ success: true, cdns: meta._cdns });\n } catch (error) {\n console.error(\"Failed to update CDNs:\", error);\n return Response.json({ error: \"Failed to update CDNs\" }, { status: 500 });\n }\n}\n","import sharp from \"sharp\";\nimport path from \"path\";\nimport fs from \"fs/promises\";\nimport { jsonResponse } from \"./utils/response\";\nimport { getPublicPath, getSrcAppPath } from \"../config\";\n\n/**\n * Generate favicon variants from a source image (streaming)\n *\n * Takes a favicon.png or favicon.jpg and generates:\n * - favicon.ico (48x48) - Classic ICO format\n * - icon.png (32x32) - Standard favicon\n * - apple-icon.png (180x180) - Apple touch icon\n *\n * All outputs are saved to src/app/ for Next.js metadata\n */\n\nconst FAVICON_CONFIGS = [\n { name: \"favicon.ico\", size: 48 },\n { name: \"icon.png\", size: 32 },\n { name: \"apple-icon.png\", size: 180 },\n];\n\nexport async function handleGenerateFavicon(request: Request) {\n const encoder = new TextEncoder();\n\n let imagePath: string;\n try {\n const body = (await request.json()) as { imagePath: string };\n imagePath = body.imagePath;\n\n if (!imagePath) {\n return jsonResponse({ error: \"No image path provided\" }, { status: 400 });\n }\n } catch {\n return jsonResponse({ error: \"Invalid request body\" }, { status: 400 });\n }\n\n // Validate filename is favicon.png or favicon.jpg\n const fileName = path.basename(imagePath).toLowerCase();\n if (fileName !== \"favicon.png\" && fileName !== \"favicon.jpg\") {\n return jsonResponse(\n {\n error: \"Source file must be named favicon.png or favicon.jpg\",\n },\n { status: 400 }\n );\n }\n\n // Build full path to source file\n const sourcePath = getPublicPath(imagePath.replace(/^\\//, \"\"));\n\n // Check if source file exists\n try {\n await fs.access(sourcePath);\n } catch {\n return jsonResponse({ error: \"Source file not found\" }, { status: 404 });\n }\n\n // Verify the source is a valid image (apply EXIF rotation for accurate dimensions)\n let metadata;\n try {\n const rotatedBuffer = await sharp(sourcePath).rotate().toBuffer();\n metadata = await sharp(rotatedBuffer).metadata();\n } catch {\n return jsonResponse(\n { error: \"Source file is not a valid image\" },\n { status: 400 }\n );\n }\n\n // Output directory is src/app/\n const outputDir = getSrcAppPath();\n\n // Check output directory exists\n try {\n await fs.access(outputDir);\n } catch {\n return jsonResponse(\n {\n error: \"Output directory src/app/ not found\",\n },\n { status: 500 }\n );\n }\n\n const stream = new ReadableStream({\n async start(controller) {\n const sendEvent = (data: object) => {\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\\n\\n`));\n };\n\n try {\n const total = FAVICON_CONFIGS.length;\n const generated: string[] = [];\n const errors: string[] = [];\n\n sendEvent({\n type: \"start\",\n total,\n sourceSize: `${metadata.width}x${metadata.height}`,\n });\n\n for (let i = 0; i < FAVICON_CONFIGS.length; i++) {\n const config = FAVICON_CONFIGS[i];\n\n try {\n const outputPath = path.join(outputDir, config.name);\n\n await sharp(sourcePath)\n .rotate() // Apply EXIF rotation\n .resize(config.size, config.size, {\n fit: \"cover\",\n position: \"center\",\n })\n .png({ quality: 100 })\n .toFile(outputPath);\n\n generated.push(config.name);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n processed: generated.length,\n percent: Math.round(((i + 1) / total) * 100),\n message: `Generated ${config.name}`,\n });\n } catch (error) {\n console.error(`Failed to generate ${config.name}:`, error);\n errors.push(config.name);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n processed: generated.length,\n percent: Math.round(((i + 1) / total) * 100),\n message: `Failed: ${config.name}`,\n });\n }\n }\n\n // Build completion message\n let message = `Generated ${generated.length} favicon${\n generated.length !== 1 ? \"s\" : \"\"\n } to src/app/.`;\n if (errors.length > 0) {\n message += ` ${errors.length} failed.`;\n }\n\n sendEvent({\n type: \"complete\",\n processed: generated.length,\n errors: errors.length,\n message,\n });\n\n controller.close();\n } catch (error) {\n console.error(\"Favicon generation error:\", error);\n sendEvent({ type: \"error\", message: \"Failed to generate favicons\" });\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n}\n","import puppeteer from \"puppeteer\";\nimport path from \"path\";\nimport fs from \"fs/promises\";\nimport sharp from \"sharp\";\nimport { config as loadEnv } from \"dotenv\";\nimport { jsonResponse } from \"./utils/response\";\nimport { getPublicPath, getWorkspacePath } from \"../config\";\nimport { loadMeta, saveMeta, setMetaEntry } from \"./utils/meta\";\n\n/**\n * Parse an env file and return key-value pairs\n */\nfunction parseEnvFile(content: string): Record<string, string> {\n const result: Record<string, string> = {};\n for (const line of content.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) continue;\n const eqIndex = trimmed.indexOf(\"=\");\n if (eqIndex > 0) {\n const key = trimmed.slice(0, eqIndex).trim();\n let value = trimmed.slice(eqIndex + 1).trim();\n // Remove quotes if present\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1);\n }\n result[key] = value;\n }\n }\n return result;\n}\n\n/**\n * Generate featured image by screenshotting the homepage (streaming)\n *\n * Takes a screenshot of the project homepage and saves it as {project-name}.jpg\n * in the public folder. Useful for social previews, README, etc.\n *\n * The project name comes from package.json \"name\" field.\n * The homepage URL can be:\n * - Provided in the request body\n * - Read from package.json \"homepage\" field\n * - Falls back to STUDIO_DEV_SITE_URL env var\n * - Falls back to http://localhost:3000\n */\n\nexport async function handleGenerateFeaturedImage(request: Request) {\n const encoder = new TextEncoder();\n\n // Parse optional URL from request body\n let customUrl: string | undefined;\n try {\n const body = (await request.json()) as { url?: string };\n customUrl = body.url;\n } catch {\n // No body or invalid JSON is fine\n }\n\n const stream = new ReadableStream({\n async start(controller) {\n const sendEvent = (data: object) => {\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\\n\\n`));\n };\n\n try {\n // Read package.json to get homepage URL\n const packageJsonPath = getWorkspacePath(\"package.json\");\n let homepageUrl =\n customUrl ||\n process.env.STUDIO_DEV_SITE_URL ||\n \"http://localhost:3000\";\n\n try {\n const packageJsonContent = await fs.readFile(packageJsonPath, \"utf8\");\n const packageJson = JSON.parse(packageJsonContent);\n if (!customUrl && packageJson.homepage) {\n homepageUrl = packageJson.homepage;\n }\n } catch {\n // package.json not found or invalid, use defaults\n }\n\n const outputPath = getPublicPath(`screenshot.jpg`);\n const relativePath = `public/screenshot.jpg`;\n\n sendEvent({\n type: \"start\",\n total: 4,\n url: homepageUrl,\n output: relativePath,\n });\n\n // Step 1: Launch browser\n sendEvent({\n type: \"progress\",\n current: 1,\n total: 4,\n percent: 25,\n message: \"Launching browser...\",\n });\n\n const browser = await puppeteer.launch({\n headless: true,\n args: [\"--no-sandbox\", \"--disable-setuid-sandbox\"],\n });\n\n try {\n // Step 2: Navigate to page\n sendEvent({\n type: \"progress\",\n current: 2,\n total: 4,\n percent: 50,\n message: `Navigating to ${homepageUrl}...`,\n });\n\n const page = await browser.newPage();\n\n // Set viewport to 2000x1000 (2:1 aspect ratio for social previews)\n await page.setViewport({\n width: 2000,\n height: 1000,\n deviceScaleFactor: 2, // Retina display quality\n });\n\n await page.goto(homepageUrl, {\n waitUntil: \"networkidle2\",\n timeout: 30000,\n });\n\n // Wait for animations/fonts to load\n await new Promise((resolve) => setTimeout(resolve, 2000));\n\n // Step 3: Take screenshot\n sendEvent({\n type: \"progress\",\n current: 3,\n total: 4,\n percent: 75,\n message: \"Taking screenshot...\",\n });\n\n await page.screenshot({\n path: outputPath,\n type: \"jpeg\",\n quality: 90,\n });\n\n // Step 4: Add to _studio.json\n sendEvent({\n type: \"progress\",\n current: 4,\n total: 4,\n percent: 95,\n message: \"Updating metadata...\",\n });\n\n // Get image dimensions using sharp\n const imageBuffer = await fs.readFile(outputPath);\n const metadata = await sharp(imageBuffer).metadata();\n const width = metadata.width || 0;\n const height = metadata.height || 0;\n\n // Add to _studio.json\n // Only set 'o' (original dimensions), not 'f' (full thumbnail)\n // because the featured image is stored at root level, not in /images/\n const meta = await loadMeta();\n const metaKey = `/screenshot.jpg`;\n setMetaEntry(meta, metaKey, {\n o: { w: width, h: height },\n });\n await saveMeta(meta);\n\n sendEvent({\n type: \"complete\",\n processed: 1,\n errors: 0,\n outputPath: relativePath,\n message: `Screenshot saved to ${relativePath}`,\n });\n } finally {\n await browser.close();\n }\n\n controller.close();\n } catch (error) {\n console.error(\"Featured image generation error:\", error);\n const errorMessage =\n error instanceof Error ? error.message : \"Unknown error\";\n sendEvent({\n type: \"error\",\n message: `Failed to generate screenshot: ${errorMessage}`,\n });\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n}\n\n/**\n * Get URL options for featured image generation\n * Returns the dev URL from .env.local and production URL from .env.production\n */\nexport async function handleGetFeaturedImageOptions() {\n try {\n const packageJsonPath = getWorkspacePath(\"package.json\");\n const envLocalPath = getWorkspacePath(\".env.local\");\n const envProductionPath = getWorkspacePath(\".env.production\");\n\n let projectName = \"featured-image\";\n let devUrl: string | null = null;\n let productionUrl: string | null = null;\n\n // Read project name from package.json\n try {\n const packageJsonContent = await fs.readFile(packageJsonPath, \"utf8\");\n const packageJson = JSON.parse(packageJsonContent);\n projectName = packageJson.name || \"featured-image\";\n } catch {\n // package.json not found or invalid\n }\n\n // Read dev URL from .env.local\n try {\n const envLocalContent = await fs.readFile(envLocalPath, \"utf8\");\n const envLocal = parseEnvFile(envLocalContent);\n devUrl = envLocal.NEXT_PUBLIC_PRODUCTION_URL || null;\n } catch {\n // .env.local not found\n }\n\n // Read production URL from .env.production\n try {\n const envProductionContent = await fs.readFile(envProductionPath, \"utf8\");\n const envProduction = parseEnvFile(envProductionContent);\n productionUrl = envProduction.NEXT_PUBLIC_PRODUCTION_URL || null;\n } catch {\n // .env.production not found\n }\n\n return jsonResponse({\n projectName,\n devUrl,\n productionUrl,\n });\n } catch (error) {\n console.error(\"Get featured image options error:\", error);\n return jsonResponse(\n { error: \"Failed to get featured image options\" },\n { status: 500 }\n );\n }\n}\n\n/**\n * Check if the featured image exists in _studio.json\n * Returns the expected filename and whether it exists\n */\nexport async function handleCheckFeaturedImage() {\n try {\n const expectedFilename = `screenshot.jpg`;\n const metaKey = `/screenshot.jpg`;\n\n // Check if the image exists in _studio.json\n const meta = await loadMeta();\n const exists = metaKey in meta && !Array.isArray(meta[metaKey]);\n\n return jsonResponse({\n filename: expectedFilename,\n exists,\n });\n } catch (error) {\n console.error(\"Check featured image error:\", error);\n return jsonResponse(\n { error: \"Failed to check featured image\" },\n { status: 500 }\n );\n }\n}\n","import { promises as fs } from \"fs\";\nimport path from \"path\";\nimport sharp from \"sharp\";\nimport type { FileItem, MetaEntry } from \"../types\";\nimport { getAllThumbnailPaths } from \"../types\";\nimport { loadMeta, saveMeta } from \"./utils/meta\";\nimport { getPublicPath, getWorkspacePath } from \"../config\";\nimport { jsonResponse } from \"./utils/response\";\nimport { isImageFile } from \"./utils/files\";\n\ninterface EditImageRequest {\n imagePath: string;\n crop: {\n x: number;\n y: number;\n width: number;\n height: number;\n };\n rotation: number; // 0, 90, 180, 270\n resize: {\n width: number;\n height: number;\n };\n quality?: number; // 1-100, defaults to 95\n}\n\n/**\n * Edit an image: crop, rotate, and resize\n * \n * Order of operations:\n * 1. Apply EXIF rotation to normalize (matches browser display)\n * 2. Extract the cropped region (crop coords are from client's view)\n * 3. Rotate the cropped result\n * 4. Resize to final dimensions\n * 5. Save and update metadata\n */\nexport async function handleEditImage(request: Request) {\n try {\n const body = (await request.json()) as EditImageRequest;\n const { imagePath, crop, rotation, resize, quality = 95 } = body;\n \n // Clamp quality to valid range\n const outputQuality = Math.max(1, Math.min(100, quality));\n\n // Validate input\n if (!imagePath || !imagePath.startsWith(\"public/\")) {\n return jsonResponse({ error: \"Invalid image path\" }, { status: 400 });\n }\n\n if (!isImageFile(path.basename(imagePath))) {\n return jsonResponse({ error: \"Not an image file\" }, { status: 400 });\n }\n\n // Check that the file exists locally\n const absolutePath = getWorkspacePath(imagePath);\n try {\n await fs.access(absolutePath);\n } catch {\n return jsonResponse(\n { error: \"Image file not found locally. Download it first.\" },\n { status: 404 }\n );\n }\n\n // Read the original image\n const imageBuffer = await fs.readFile(absolutePath);\n \n // Step 1: Apply EXIF rotation first to normalize the image\n // This matches what the browser displays\n const exifCorrectedBuffer = await sharp(imageBuffer).rotate().toBuffer();\n const exifMeta = await sharp(exifCorrectedBuffer).metadata();\n const exifWidth = exifMeta.width || 0;\n const exifHeight = exifMeta.height || 0;\n\n // Step 2: Apply crop FIRST (extract region)\n // Crop coordinates are from the client's view (EXIF-corrected, non-rotated)\n const cropX = Math.max(0, Math.min(crop.x, exifWidth - 1));\n const cropY = Math.max(0, Math.min(crop.y, exifHeight - 1));\n const cropWidth = Math.min(crop.width, exifWidth - cropX);\n const cropHeight = Math.min(crop.height, exifHeight - cropY);\n\n let pipeline = sharp(exifCorrectedBuffer);\n \n // Only extract if crop is different from full image\n if (cropX > 0 || cropY > 0 || cropWidth < exifWidth || cropHeight < exifHeight) {\n pipeline = pipeline.extract({\n left: Math.round(cropX),\n top: Math.round(cropY),\n width: Math.round(cropWidth),\n height: Math.round(cropHeight),\n });\n }\n\n // Step 3: Apply user rotation if any (90° increments) AFTER cropping\n if (rotation !== 0) {\n pipeline = pipeline.rotate(rotation);\n }\n\n // Step 4: Apply resize (if different from expected output)\n // Note: After rotation, dimensions may have swapped\n pipeline = pipeline.resize(resize.width, resize.height);\n\n // Determine output format based on original file extension\n const ext = path.extname(imagePath).toLowerCase();\n let finalBuffer: Buffer;\n \n if (ext === \".png\") {\n // PNG is lossless, compressionLevel doesn't affect quality\n finalBuffer = await pipeline.png({ compressionLevel: 9 }).toBuffer();\n } else if (ext === \".webp\") {\n finalBuffer = await pipeline.webp({ quality: outputQuality, lossless: outputQuality === 100 }).toBuffer();\n } else if (ext === \".gif\") {\n finalBuffer = await pipeline.gif().toBuffer();\n } else {\n // Default to JPEG for jpg/jpeg\n finalBuffer = await pipeline.jpeg({ quality: outputQuality, mozjpeg: true }).toBuffer();\n }\n\n // Get final dimensions\n const finalMeta = await sharp(finalBuffer).metadata();\n const finalWidth = finalMeta.width || resize.width;\n const finalHeight = finalMeta.height || resize.height;\n\n // 4. Save the edited image (overwrite original)\n await fs.writeFile(absolutePath, finalBuffer);\n\n // 5. Update metadata\n const meta = await loadMeta();\n const imageKey = \"/\" + imagePath.replace(/^public\\//, \"\");\n const entry = meta[imageKey] as MetaEntry | undefined;\n\n // Update dimensions\n const updatedEntry: MetaEntry = {\n ...entry,\n o: { w: finalWidth, h: finalHeight },\n };\n\n // Clear thumbnail dimensions (they need regeneration)\n delete updatedEntry.sm;\n delete updatedEntry.md;\n delete updatedEntry.lg;\n delete updatedEntry.f;\n\n meta[imageKey] = updatedEntry;\n await saveMeta(meta);\n\n // 6. Delete old thumbnails\n const thumbnailPaths = getAllThumbnailPaths(imageKey);\n for (const thumbPath of thumbnailPaths) {\n const absoluteThumbPath = getPublicPath(thumbPath);\n try {\n await fs.unlink(absoluteThumbPath);\n } catch {\n // Thumbnail might not exist\n }\n }\n\n // 7. Build updated FileItem for response\n const stats = await fs.stat(absolutePath);\n const updatedItem: Partial<FileItem> = {\n name: path.basename(imagePath),\n path: imagePath,\n type: \"file\",\n size: stats.size,\n dimensions: { width: finalWidth, height: finalHeight },\n hasSm: false,\n hasMd: false,\n hasLg: false,\n hasFull: false,\n hasThumbnail: false,\n // Preserve CDN status from original entry\n cdnPushed: entry?.c !== undefined,\n isRemote: false, // If we're editing, it's local\n hasUpdate: entry?.c !== undefined, // If was on CDN, now has local update\n };\n\n return jsonResponse({\n success: true,\n updatedItem,\n dimensions: { width: finalWidth, height: finalHeight },\n });\n } catch (error) {\n console.error(\"Edit image error:\", error);\n const message = error instanceof Error ? error.message : \"Unknown error\";\n return jsonResponse(\n { error: `Failed to edit image: ${message}` },\n { status: 500 }\n );\n }\n}\n","import { promises as fs } from 'fs'\nimport path from 'path'\nimport { getWorkspacePath } from '../config'\nimport { jsonResponse } from './utils/response'\nimport type { FileItem } from '../types'\n\n/**\n * List files and folders for fonts paths\n * Works like the regular list handler but only for _fonts/\n */\nexport async function handleFontsList(request: Request): Promise<Response> {\n const searchParams = new URL(request.url).searchParams\n const requestedPath = searchParams.get('path') || '_fonts'\n\n try {\n const items: FileItem[] = []\n\n // Only allow paths within _fonts/\n const isAllowed = requestedPath === '_fonts' || requestedPath.startsWith('_fonts/')\n\n if (!isAllowed) {\n return jsonResponse({ items: [], error: 'Path not allowed' }, { status: 400 })\n }\n\n const fsPath = getWorkspacePath(requestedPath)\n\n // Check if directory exists\n try {\n const stat = await fs.stat(fsPath)\n if (!stat.isDirectory()) {\n return jsonResponse({ items: [] })\n }\n } catch {\n // Directory doesn't exist\n return jsonResponse({ items: [], canCreate: true })\n }\n\n // Read directory contents\n const entries = await fs.readdir(fsPath, { withFileTypes: true })\n\n for (const entry of entries) {\n const itemPath = `${requestedPath}/${entry.name}`\n\n if (entry.isDirectory()) {\n // Count files in folder\n let fileCount = 0\n try {\n const subEntries = await fs.readdir(path.join(fsPath, entry.name))\n fileCount = subEntries.filter(f => \n f.match(/\\.(ttf|woff2?|otf|ts|tsx|js)$/i)\n ).length\n } catch {\n // Ignore errors counting\n }\n\n items.push({\n name: entry.name,\n path: itemPath,\n type: 'folder',\n fileCount,\n })\n } else {\n // Only show font files and ts/js files\n const ext = path.extname(entry.name).toLowerCase()\n const allowedExts = ['.ttf', '.woff', '.woff2', '.otf', '.ts', '.tsx', '.js']\n\n if (allowedExts.includes(ext)) {\n // Get file size\n let size = 0\n try {\n const fileStat = await fs.stat(path.join(fsPath, entry.name))\n size = fileStat.size\n } catch {\n // Ignore\n }\n\n items.push({\n name: entry.name,\n path: itemPath,\n type: 'file',\n size,\n })\n }\n }\n }\n\n // Sort: folders first, then alphabetically\n items.sort((a, b) => {\n if (a.type === 'folder' && b.type !== 'folder') return -1\n if (a.type !== 'folder' && b.type === 'folder') return 1\n return a.name.localeCompare(b.name)\n })\n\n return jsonResponse({ items })\n } catch (error) {\n console.error('Error listing fonts:', error)\n return jsonResponse({ error: 'Failed to list fonts' }, { status: 500 })\n }\n}\n\n/**\n * Upload TTF font files\n */\nexport async function handleFontsUpload(request: Request): Promise<Response> {\n try {\n const formData = await request.formData()\n const file = formData.get('file') as File | null\n const targetPath = formData.get('path') as string || '_fonts'\n\n if (!file) {\n return jsonResponse({ error: 'No file provided' }, { status: 400 })\n }\n\n // Only accept TTF files\n if (!file.name.toLowerCase().endsWith('.ttf')) {\n return jsonResponse({ error: 'Only TTF files are supported' }, { status: 400 })\n }\n\n // Validate path\n if (!targetPath.startsWith('_fonts')) {\n return jsonResponse({ error: 'Can only upload to _fonts/' }, { status: 400 })\n }\n\n const bytes = await file.arrayBuffer()\n const buffer = Buffer.from(bytes)\n\n // Ensure directory exists\n const uploadDir = getWorkspacePath(targetPath)\n await fs.mkdir(uploadDir, { recursive: true })\n\n // Save file\n const filePath = path.join(uploadDir, file.name.toLowerCase())\n await fs.writeFile(filePath, buffer)\n\n return jsonResponse({\n success: true,\n path: `${targetPath}/${file.name.toLowerCase()}`,\n })\n } catch (error) {\n console.error('Error uploading font:', error)\n return jsonResponse({ error: 'Failed to upload font' }, { status: 500 })\n }\n}\n\n/**\n * Create a folder in _fonts\n */\nexport async function handleFontsCreateFolder(request: Request): Promise<Response> {\n try {\n const { path: targetPath, name } = await request.json()\n\n if (!targetPath || !name) {\n return jsonResponse({ error: 'Path and name are required' }, { status: 400 })\n }\n\n // Only allow paths within _fonts/\n const isAllowed = targetPath === '_fonts' || targetPath.startsWith('_fonts/')\n\n if (!isAllowed) {\n return jsonResponse({ error: 'Path not allowed' }, { status: 400 })\n }\n\n const folderPath = getWorkspacePath(targetPath, name.toLowerCase())\n await fs.mkdir(folderPath, { recursive: true })\n\n return jsonResponse({\n success: true,\n path: `${targetPath}/${name.toLowerCase()}`,\n })\n } catch (error) {\n console.error('Error creating folder:', error)\n return jsonResponse({ error: 'Failed to create folder' }, { status: 500 })\n }\n}\n\n/**\n * Delete files or folders from _fonts\n */\nexport async function handleFontsDelete(request: Request): Promise<Response> {\n try {\n const { paths } = await request.json()\n\n if (!paths || !Array.isArray(paths) || paths.length === 0) {\n return jsonResponse({ error: 'Paths are required' }, { status: 400 })\n }\n\n // Validate all paths - only allow within _fonts/\n for (const p of paths) {\n if (!p.startsWith('_fonts/')) {\n return jsonResponse({ error: `Path not allowed: ${p}` }, { status: 400 })\n }\n }\n\n const deleted: string[] = []\n const errors: string[] = []\n\n for (const p of paths) {\n try {\n const fullPath = getWorkspacePath(p)\n const stat = await fs.stat(fullPath)\n\n if (stat.isDirectory()) {\n await fs.rm(fullPath, { recursive: true })\n } else {\n await fs.unlink(fullPath)\n }\n deleted.push(p)\n } catch (err) {\n errors.push(`Failed to delete ${p}: ${err instanceof Error ? err.message : 'Unknown error'}`)\n }\n }\n\n return jsonResponse({\n success: true,\n deleted,\n errors: errors.length > 0 ? errors : undefined,\n })\n } catch (error) {\n console.error('Error deleting:', error)\n return jsonResponse({ error: 'Failed to delete' }, { status: 500 })\n }\n}\n\n/**\n * Rename a folder in _fonts\n */\nexport async function handleFontsRename(request: Request): Promise<Response> {\n try {\n const { oldPath, newName } = await request.json()\n\n if (!oldPath || !newName) {\n return jsonResponse({ error: 'oldPath and newName are required' }, { status: 400 })\n }\n\n // Only allow renaming within _fonts/\n if (!oldPath.startsWith('_fonts/')) {\n return jsonResponse({ error: 'Can only rename items in _fonts/' }, { status: 400 })\n }\n\n // Validate newName (no slashes, etc.)\n if (newName.includes('/') || newName.includes('\\\\')) {\n return jsonResponse({ error: 'Invalid folder name' }, { status: 400 })\n }\n\n const oldFullPath = getWorkspacePath(oldPath)\n \n // Get the parent directory and construct new path\n const parentDir = path.dirname(oldPath)\n const newPath = `${parentDir}/${newName.toLowerCase()}`\n const newFullPath = getWorkspacePath(newPath)\n\n // Check if old path exists\n try {\n await fs.stat(oldFullPath)\n } catch {\n return jsonResponse({ error: 'Path not found' }, { status: 404 })\n }\n\n // Check if new path already exists\n try {\n await fs.stat(newFullPath)\n return jsonResponse({ error: 'A folder with that name already exists' }, { status: 400 })\n } catch {\n // Good, it doesn't exist\n }\n\n // Rename\n await fs.rename(oldFullPath, newFullPath)\n\n return jsonResponse({\n success: true,\n oldPath,\n newPath,\n })\n } catch (error) {\n console.error('Error renaming:', error)\n return jsonResponse({ error: 'Failed to rename' }, { status: 500 })\n }\n}\n"],"mappings":"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","names":["fs","path","path","fs","path","path","fs","fs","ext","base","outputExt","resolve","fs","fs","path","fs","path","sharp","fs","path","path","fs","fs","path","fs","path","S3Client","PutObjectCommand","DeleteObjectCommand","S3Client","fs","PutObjectCommand","path","fs","path","fs","path","S3Client","DeleteObjectCommand","PutObjectCommand","path","fs","sharp","newPath","meta","oldKey","entry","newKey","oldThumbFolder","isInCloud","fileCdnUrl","isInOurR2","hasThumbnails","fs","path","sharp","fs","path","sharp","localPath","sharp","path","sharp","sharp","path","fs","path","fs","sharp","fs","sharp","packageJsonPath","fs","packageJson","resolve","sharp","fs","path","sharp","path","fs","sharp","fs","path","fs","path","resolve"]}
1
+ {"version":3,"sources":["../../src/server/index.ts","../../src/handlers/list.ts","../../src/handlers/utils/meta.ts","../../src/config/workspace.ts","../../src/handlers/utils/files.ts","../../src/handlers/utils/thumbnails.ts","../../src/handlers/utils/cdn.ts","../../src/types.ts","../../src/handlers/utils/response.ts","../../src/handlers/files.ts","../../src/handlers/utils/folders.ts","../../src/handlers/images.ts","../../src/handlers/scan.ts","../../src/handlers/import.ts","../../src/handlers/favicon.ts","../../src/handlers/featured-image.ts","../../src/handlers/edit-image.ts","../../src/handlers/fonts.ts"],"sourcesContent":["import express, { Request, Response } from \"express\";\nimport { resolve, join } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { dirname } from \"path\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { config as loadEnv } from \"dotenv\";\nimport { createServer } from \"net\";\n\n// Import handlers from individual modules\nimport {\n handleList,\n handleSearch,\n handleListFolders,\n handleCountImages,\n handleFolderImages,\n} from \"../handlers/list\";\nimport {\n handleUpload,\n handleDelete,\n handleDeleteStream,\n handleCreateFolder,\n handleRename,\n handleRenameStream,\n handleMoveStream,\n} from \"../handlers/files\";\nimport {\n handleSync,\n handleSyncStream,\n handleReprocessStream,\n handleUnprocessStream,\n handleDownloadStream,\n handlePushUpdatesStream,\n handleCancelUpdates,\n handleCancelStreamOperation,\n} from \"../handlers/images\";\nimport { handleScanStream, handleDeleteOrphans } from \"../handlers/scan\";\nimport {\n handleImportUrls,\n handleGetCdns,\n handleUpdateCdns,\n} from \"../handlers/import\";\nimport { handleGenerateFavicon } from \"../handlers/favicon\";\nimport {\n handleGenerateFeaturedImage,\n handleCheckFeaturedImage,\n handleGetFeaturedImageOptions,\n} from \"../handlers/featured-image\";\nimport { handleEditImage } from \"../handlers/edit-image\";\nimport { handleFontsList, handleFontsUpload, handleFontsCreateFolder, handleFontsDelete, handleFontsRename } from \"../handlers/fonts\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n// Get version from package.json\nconst packageJsonPath = resolve(__dirname, \"../../package.json\");\nconst packageJson = JSON.parse(readFileSync(packageJsonPath, \"utf-8\"));\nconst version = packageJson.version;\n\nexport interface ServerOptions {\n port: number;\n workspace: string;\n open?: boolean;\n}\n\n/**\n * Check if a port is available\n */\nfunction isPortAvailable(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const server = createServer();\n server.once(\"error\", () => {\n resolve(false);\n });\n server.once(\"listening\", () => {\n server.close();\n resolve(true);\n });\n server.listen(port);\n });\n}\n\n/**\n * Find an available port starting from the given port\n */\nasync function findAvailablePort(\n startPort: number,\n maxAttempts = 10\n): Promise<number> {\n for (let i = 0; i < maxAttempts; i++) {\n const port = startPort + i;\n if (await isPortAvailable(port)) {\n return port;\n }\n }\n throw new Error(\n `No available port found between ${startPort} and ${\n startPort + maxAttempts - 1\n }`\n );\n}\n\nexport async function startServer(options: ServerOptions) {\n const { port: requestedPort, workspace, open } = options;\n\n // Find an available port starting from the requested port\n const port = await findAvailablePort(requestedPort);\n if (port !== requestedPort) {\n console.log(`Port ${requestedPort} is in use, using port ${port} instead`);\n }\n\n const app = express();\n\n // Store workspace in a way handlers can access\n process.env.STUDIO_WORKSPACE = workspace;\n\n // Load environment variables from workspace\n // Load .env.local (contains both R2 credentials and local dev URL)\n const envLocalPath = join(workspace, \".env.local\");\n\n if (existsSync(envLocalPath)) {\n loadEnv({ path: envLocalPath, quiet: true });\n }\n\n // Also set STUDIO_DEV_SITE_URL from NEXT_PUBLIC_PRODUCTION_URL if not already set\n if (\n !process.env.STUDIO_DEV_SITE_URL &&\n process.env.NEXT_PUBLIC_PRODUCTION_URL\n ) {\n process.env.STUDIO_DEV_SITE_URL = process.env.NEXT_PUBLIC_PRODUCTION_URL;\n }\n\n // Middleware - skip JSON parsing for upload routes (needs raw body for FormData)\n const rawBodyPaths = [\"/api/studio/upload\", \"/api/studio/fonts/upload\"];\n app.use((req, res, next) => {\n if (rawBodyPaths.includes(req.path)) {\n next();\n } else {\n express.json({ limit: \"50mb\" })(req, res, next);\n }\n });\n app.use((req, res, next) => {\n if (rawBodyPaths.includes(req.path)) {\n next();\n } else {\n express.urlencoded({ extended: true, limit: \"50mb\" })(req, res, next);\n }\n });\n\n // API Routes - GET endpoints\n app.get(\"/api/studio/list\", wrapHandler(handleList));\n app.get(\"/api/studio/list-folders\", wrapHandler(handleListFolders));\n app.get(\"/api/studio/search\", wrapHandler(handleSearch));\n app.get(\"/api/studio/count-images\", wrapHandler(handleCountImages));\n app.get(\"/api/studio/folder-images\", wrapHandler(handleFolderImages));\n app.get(\"/api/studio/cdns\", wrapHandler(handleGetCdns));\n app.get(\n \"/api/studio/check-featured-image\",\n wrapHandler(handleCheckFeaturedImage)\n );\n app.get(\n \"/api/studio/featured-image-options\",\n wrapHandler(handleGetFeaturedImageOptions)\n );\n\n // Font management routes\n app.get(\"/api/studio/fonts/list\", wrapHandler(handleFontsList));\n app.post(\"/api/studio/fonts/upload\", wrapRawHandler(handleFontsUpload));\n app.post(\"/api/studio/fonts/create-folder\", wrapHandler(handleFontsCreateFolder));\n app.post(\"/api/studio/fonts/delete\", wrapHandler(handleFontsDelete));\n app.post(\"/api/studio/fonts/rename\", wrapHandler(handleFontsRename));\n\n // API Routes - POST endpoints\n // Upload uses raw body wrapper to preserve FormData\n app.post(\"/api/studio/upload\", wrapRawHandler(handleUpload));\n app.post(\"/api/studio/create-folder\", wrapHandler(handleCreateFolder));\n app.post(\"/api/studio/rename\", wrapHandler(handleRename));\n app.post(\"/api/studio/rename-stream\", wrapHandler(handleRenameStream, true));\n app.post(\"/api/studio/move\", wrapHandler(handleMoveStream, true));\n app.post(\"/api/studio/edit-image\", wrapHandler(handleEditImage));\n app.post(\"/api/studio/sync\", wrapHandler(handleSync, true));\n app.post(\"/api/studio/sync-stream\", wrapHandler(handleSyncStream, true));\n app.post(\n \"/api/studio/reprocess-stream\",\n wrapHandler(handleReprocessStream, true)\n );\n app.post(\n \"/api/studio/unprocess-stream\",\n wrapHandler(handleUnprocessStream, true)\n );\n app.post(\n \"/api/studio/download-stream\",\n wrapHandler(handleDownloadStream, true)\n );\n app.post(\n \"/api/studio/push-updates-stream\",\n wrapHandler(handlePushUpdatesStream, true)\n );\n app.post(\"/api/studio/cancel-updates\", wrapHandler(handleCancelUpdates));\n app.post(\n \"/api/studio/cancel-stream\",\n wrapHandler(handleCancelStreamOperation)\n );\n app.post(\"/api/studio/scan\", wrapHandler(handleScanStream, true));\n app.post(\"/api/studio/delete-orphans\", wrapHandler(handleDeleteOrphans));\n app.post(\"/api/studio/import\", wrapHandler(handleImportUrls, true));\n app.post(\"/api/studio/cdns\", wrapHandler(handleUpdateCdns));\n app.post(\n \"/api/studio/generate-favicon\",\n wrapHandler(handleGenerateFavicon, true)\n );\n app.post(\n \"/api/studio/generate-featured-image\",\n wrapHandler(handleGenerateFeaturedImage, true)\n );\n\n // API Routes - DELETE endpoints\n app.post(\"/api/studio/delete\", wrapHandler(handleDelete));\n app.post(\"/api/studio/delete-stream\", wrapHandler(handleDeleteStream, true));\n\n // Serve static files from workspace's public folder\n // Files are accessed at root path (e.g., /favicon.png, /images/photo.jpg)\n app.use(express.static(join(workspace, \"public\")));\n\n // Serve the client app\n const clientDir = resolve(__dirname, \"../client\");\n\n // Inject workspace and dev URL into the HTML\n app.get(\"/\", (req: Request, res: Response) => {\n const htmlPath = join(clientDir, \"index.html\");\n if (existsSync(htmlPath)) {\n let html = readFileSync(htmlPath, \"utf-8\");\n // Inject workspace and site URL as global variables\n const siteUrl = process.env.STUDIO_DEV_SITE_URL || \"\";\n const script = `<script>\n window.__STUDIO_WORKSPACE__ = ${JSON.stringify(workspace)};\n window.__STUDIO_SITE_URL__ = ${JSON.stringify(siteUrl)};\n </script>`;\n html = html.replace(\"</head>\", `${script}</head>`);\n res.type(\"html\").send(html);\n } else {\n res.status(404).send(\"Client not built. Run npm run build first.\");\n }\n });\n\n // Serve other static assets\n app.use(express.static(clientDir));\n\n // Start server\n const title = `Gallop - Studio (${version})`;\n app.listen(port, () => {\n console.log(`\n┌─────────────────────────────────────┐\n│ ${title.padEnd(34)}│\n├─────────────────────────────────────┤\n│ Workspace: ${\n workspace.length > 24\n ? \"...\" + workspace.slice(-21)\n : workspace.padEnd(24)\n }│\n│ URL: http://localhost:${port} │\n└─────────────────────────────────────┘\n`);\n\n if (open) {\n import(\"open\")\n .then((mod) => {\n mod.default(`http://localhost:${port}`);\n })\n .catch(() => {\n // open package might not be available\n });\n }\n });\n}\n\n// Wrapper to adapt Next.js-style handlers to Express\nfunction wrapHandler(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handler: (request?: any) => Promise<globalThis.Response>,\n streaming = false\n) {\n return async (req: Request, res: Response) => {\n try {\n const request = createFetchRequest(req);\n const response = await handler(request);\n if (streaming) {\n await sendStreamingResponse(res, response);\n } else {\n await sendResponse(res, response);\n }\n } catch (error) {\n console.error(\"Handler error:\", error);\n res.status(500).json({ error: \"Internal server error\" });\n }\n };\n}\n\n// Wrapper for handlers that need raw body (like file uploads with FormData)\nfunction wrapRawHandler(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handler: (request?: any) => Promise<globalThis.Response>\n) {\n return async (req: Request, res: Response) => {\n try {\n const request = await createRawFetchRequest(req);\n const response = await handler(request);\n await sendResponse(res, response);\n } catch (error) {\n console.error(\"Handler error:\", error);\n res.status(500).json({ error: \"Internal server error\" });\n }\n };\n}\n\n// Helper to create a Fetch API Request from Express request\nfunction createFetchRequest(req: Request): globalThis.Request {\n const url = new URL(req.url, `http://${req.headers.host}`);\n\n const headers = new Headers();\n for (const [key, value] of Object.entries(req.headers)) {\n if (value) {\n if (Array.isArray(value)) {\n value.forEach((v) => headers.append(key, v));\n } else {\n headers.set(key, value);\n }\n }\n }\n\n const init: RequestInit = {\n method: req.method,\n headers,\n };\n\n // Add body for non-GET requests\n if (req.method !== \"GET\" && req.method !== \"HEAD\") {\n if (req.body) {\n init.body = JSON.stringify(req.body);\n }\n }\n\n return new globalThis.Request(url.toString(), init);\n}\n\n// Helper to create a Fetch API Request with raw body (for FormData uploads)\nasync function createRawFetchRequest(\n req: Request\n): Promise<globalThis.Request> {\n const url = new URL(req.url, `http://${req.headers.host}`);\n\n const headers = new Headers();\n for (const [key, value] of Object.entries(req.headers)) {\n if (value) {\n if (Array.isArray(value)) {\n value.forEach((v) => headers.append(key, v));\n } else {\n headers.set(key, value);\n }\n }\n }\n\n // Collect raw body chunks\n const chunks: Buffer[] = [];\n for await (const chunk of req) {\n chunks.push(Buffer.from(chunk));\n }\n const body = Buffer.concat(chunks);\n\n const init: RequestInit = {\n method: req.method,\n headers,\n body,\n };\n\n return new globalThis.Request(url.toString(), init);\n}\n\n// Helper to send a Response to Express response\nasync function sendResponse(res: Response, response: globalThis.Response) {\n res.status(response.status);\n\n // Copy headers\n response.headers.forEach((value, key) => {\n res.setHeader(key, value);\n });\n\n // Send body\n const body = await response.text();\n res.send(body);\n}\n\n// Helper to send a streaming Response to Express response\nasync function sendStreamingResponse(\n res: Response,\n response: globalThis.Response\n) {\n res.status(response.status);\n\n // Copy headers\n response.headers.forEach((value, key) => {\n res.setHeader(key, value);\n });\n\n // Check if it's a streaming response\n if (response.body) {\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n res.write(decoder.decode(value, { stream: true }));\n }\n res.end();\n } catch (error) {\n console.error(\"Streaming error:\", error);\n res.end();\n }\n } else {\n const body = await response.text();\n res.send(body);\n }\n}\n","import { promises as fs } from \"fs\";\nimport path from \"path\";\nimport type { FileItem, MetaEntry } from \"../types\";\nimport { loadMeta, isImageFile, getCdnUrls, getFileEntries } from \"./utils\";\nimport { getThumbnailPath, isProcessed } from \"../types\";\nimport { getPublicPath, getWorkspacePath } from \"../config\";\nimport { jsonResponse } from \"./utils/response\";\n\n/**\n * Get all thumbnail file info for a processed meta entry\n * Returns the thumbnail paths that exist based on which dimension properties are present\n */\nfunction getExistingThumbnails(\n originalPath: string,\n entry: MetaEntry\n): Array<{ path: string; size: \"f\" | \"lg\" | \"md\" | \"sm\" }> {\n const thumbnails: Array<{ path: string; size: \"f\" | \"lg\" | \"md\" | \"sm\" }> =\n [];\n\n if (entry.f) {\n thumbnails.push({\n path: getThumbnailPath(originalPath, \"full\"),\n size: \"f\",\n });\n }\n if (entry.lg) {\n thumbnails.push({ path: getThumbnailPath(originalPath, \"lg\"), size: \"lg\" });\n }\n if (entry.md) {\n thumbnails.push({ path: getThumbnailPath(originalPath, \"md\"), size: \"md\" });\n }\n if (entry.sm) {\n thumbnails.push({ path: getThumbnailPath(originalPath, \"sm\"), size: \"sm\" });\n }\n\n return thumbnails;\n}\n\n/**\n * Count cloud, remote, and local files for a folder prefix\n */\nfunction countFileTypes(\n folderPrefix: string,\n fileEntries: [string, MetaEntry][],\n cdnUrls: string[],\n r2PublicUrl: string\n): {\n cloudCount: number;\n remoteCount: number;\n localCount: number;\n updateCount: number;\n} {\n let cloudCount = 0;\n let remoteCount = 0;\n let localCount = 0;\n let updateCount = 0;\n\n for (const [key, entry] of fileEntries) {\n if (key.startsWith(folderPrefix)) {\n if (entry.c !== undefined) {\n // Check if it's our R2 or a remote URL (normalize trailing slashes)\n const cdnUrl = cdnUrls[entry.c]?.replace(/\\/$/, \"\") || \"\";\n if (cdnUrl === r2PublicUrl) {\n cloudCount++;\n } else {\n remoteCount++;\n }\n } else {\n localCount++;\n }\n // Count pending updates\n if (entry.u === 1) {\n updateCount++;\n }\n }\n }\n\n return { cloudCount, remoteCount, localCount, updateCount };\n}\n\n/**\n * List files and folders from meta\n * Folders are derived from file paths in meta AND filesystem\n */\nexport async function handleList(request: Request) {\n const searchParams = new URL(request.url).searchParams;\n const requestedPath = searchParams.get(\"path\") || \"public\";\n\n try {\n const meta = await loadMeta();\n const fileEntries = getFileEntries(meta);\n const cdnUrls = getCdnUrls(meta);\n const r2PublicUrl =\n process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/$/, \"\") || \"\";\n\n // Normalize the requested path to match meta keys\n // requestedPath is like \"public\" or \"public/photos\"\n // meta keys are like \"/photos/image.jpg\"\n const relativePath = requestedPath.replace(/^public\\/?/, \"\");\n const pathPrefix = relativePath ? `/${relativePath}/` : \"/\";\n\n const items: FileItem[] = [];\n const seenFolders = new Set<string>();\n const metaKeys = fileEntries.map(([key]) => key);\n\n // Check if we're inside the images folder (protected area)\n const isInsideImagesFolder =\n relativePath === \"images\" || relativePath.startsWith(\"images/\");\n\n // For the images folder, derive contents from meta entries with thumbnails\n if (isInsideImagesFolder) {\n // Get the path within images folder (e.g., \"images/subfolder\" -> \"subfolder\")\n const imagesSubPath = relativePath.replace(/^images\\/?/, \"\");\n const imagesPrefix = imagesSubPath ? `/${imagesSubPath}/` : \"/\";\n\n // Collect all thumbnails from processed entries\n const allThumbnails: Array<{\n path: string;\n size: \"f\" | \"lg\" | \"md\" | \"sm\";\n originalKey: string;\n }> = [];\n\n for (const [key, entry] of fileEntries) {\n if (isProcessed(entry)) {\n const thumbnails = getExistingThumbnails(key, entry);\n for (const thumb of thumbnails) {\n allThumbnails.push({ ...thumb, originalKey: key });\n }\n }\n }\n\n // Filter thumbnails that are in the current images subfolder\n for (const thumb of allThumbnails) {\n // thumb.path is like \"/images/photos/image.jpg\" or \"/images/photos/image-lg.jpg\"\n // We need to check if it's under the current images path\n const thumbRelative = thumb.path.replace(/^\\/images\\/?/, \"\");\n\n // Get the original entry to check if it's on CDN\n const originalEntry = fileEntries.find(\n ([k]) => k === thumb.originalKey\n )?.[1];\n const cdnIndex = originalEntry?.c;\n const cdnBaseUrl =\n cdnIndex !== undefined ? cdnUrls[cdnIndex] : undefined;\n // Build the full thumbnail URL (with CDN base if applicable)\n const thumbnailUrl = cdnBaseUrl\n ? `${cdnBaseUrl}${thumb.path}`\n : thumb.path;\n // Determine if it's pushed to CDN and if it's remote (not our R2)\n const isPushedToCloud = cdnIndex !== undefined;\n const normalizedCdnBaseUrl = cdnBaseUrl?.replace(/\\/$/, \"\") || \"\";\n const isRemote =\n isPushedToCloud && normalizedCdnBaseUrl !== r2PublicUrl;\n\n // Get dimensions for this thumbnail size\n const thumbDims = originalEntry?.[thumb.size];\n const dimensions = thumbDims\n ? { width: thumbDims.w, height: thumbDims.h }\n : undefined;\n\n // Check if this is directly in the current folder or in a subfolder\n if (imagesSubPath === \"\") {\n // We're at /images root\n const slashIndex = thumbRelative.indexOf(\"/\");\n if (slashIndex === -1) {\n // Direct file in images root\n const fileName = thumbRelative;\n items.push({\n name: fileName,\n path: `public/images/${fileName}`,\n type: \"file\",\n thumbnail: thumbnailUrl,\n hasThumbnail: false,\n isProtected: true,\n cdnPushed: isPushedToCloud,\n cdnBaseUrl,\n isCloud: isPushedToCloud && !isRemote,\n isRemote,\n dimensions,\n });\n } else {\n // In a subfolder - add the folder\n const folderName = thumbRelative.slice(0, slashIndex);\n if (!seenFolders.has(folderName)) {\n seenFolders.add(folderName);\n // Count thumbnails in this folder with cloud/local breakdown\n const folderPrefix = `/${folderName}/`;\n const folderThumbs = allThumbnails.filter((t) =>\n t.path.replace(/^\\/images/, \"\").startsWith(folderPrefix)\n );\n let folderCloudCount = 0;\n let folderRemoteCount = 0;\n let folderLocalCount = 0;\n for (const ft of folderThumbs) {\n const origEntry = meta[ft.originalKey] as MetaEntry | undefined;\n if (origEntry?.c !== undefined) {\n const entryCdnUrl = cdnUrls[origEntry.c]?.replace(/\\/?$/, \"\");\n if (r2PublicUrl && entryCdnUrl === r2PublicUrl) {\n folderCloudCount++;\n } else {\n folderRemoteCount++;\n }\n } else {\n folderLocalCount++;\n }\n }\n items.push({\n name: folderName,\n path: `public/images/${folderName}`,\n type: \"folder\",\n fileCount: folderThumbs.length,\n cloudCount: folderCloudCount,\n remoteCount: folderRemoteCount,\n localCount: folderLocalCount,\n isProtected: true,\n });\n }\n }\n } else {\n // We're in a subfolder of images\n if (\n !thumbRelative.startsWith(imagesSubPath + \"/\") &&\n thumbRelative !== imagesSubPath\n )\n continue;\n\n const remaining = thumbRelative.slice(imagesSubPath.length + 1);\n if (!remaining) continue;\n\n const slashIndex = remaining.indexOf(\"/\");\n if (slashIndex === -1) {\n // Direct file\n items.push({\n name: remaining,\n path: `public/images/${imagesSubPath}/${remaining}`,\n type: \"file\",\n thumbnail: thumbnailUrl,\n hasThumbnail: false,\n isProtected: true,\n cdnPushed: isPushedToCloud,\n cdnBaseUrl,\n isCloud: isPushedToCloud && !isRemote,\n isRemote,\n dimensions,\n });\n } else {\n // Subfolder\n const folderName = remaining.slice(0, slashIndex);\n if (!seenFolders.has(folderName)) {\n seenFolders.add(folderName);\n const folderPrefix = `${imagesSubPath}/${folderName}/`;\n const folderThumbs = allThumbnails.filter((t) =>\n t.path.replace(/^\\/images\\//, \"\").startsWith(folderPrefix)\n );\n let subCloudCount = 0;\n let subRemoteCount = 0;\n let subLocalCount = 0;\n for (const ft of folderThumbs) {\n const origEntry = meta[ft.originalKey] as MetaEntry | undefined;\n if (origEntry?.c !== undefined) {\n const entryCdnUrl = cdnUrls[origEntry.c]?.replace(/\\/?$/, \"\");\n if (r2PublicUrl && entryCdnUrl === r2PublicUrl) {\n subCloudCount++;\n } else {\n subRemoteCount++;\n }\n } else {\n subLocalCount++;\n }\n }\n items.push({\n name: folderName,\n path: `public/images/${imagesSubPath}/${folderName}`,\n type: \"folder\",\n fileCount: folderThumbs.length,\n cloudCount: subCloudCount,\n remoteCount: subRemoteCount,\n localCount: subLocalCount,\n isProtected: true,\n });\n }\n }\n }\n }\n\n return jsonResponse({ items });\n }\n\n // Not in images folder - check filesystem for folders (including empty ones)\n const absoluteDir = getWorkspacePath(requestedPath);\n try {\n const dirEntries = await fs.readdir(absoluteDir, { withFileTypes: true });\n for (const entry of dirEntries) {\n if (entry.name.startsWith(\".\")) continue;\n\n if (entry.isDirectory()) {\n if (!seenFolders.has(entry.name)) {\n seenFolders.add(entry.name);\n\n // Check if this folder is the images folder\n const isImagesFolder = entry.name === \"images\" && !relativePath;\n const folderPath = relativePath\n ? `public/${relativePath}/${entry.name}`\n : `public/${entry.name}`;\n\n // Count files in this folder\n let fileCount = 0;\n let cloudCount = 0;\n let remoteCount = 0;\n let localCount = 0;\n let updateCount = 0;\n\n if (isImagesFolder) {\n // Count thumbnails from meta for images folder\n for (const [key, metaEntry] of fileEntries) {\n if (isProcessed(metaEntry)) {\n const thumbCount = getExistingThumbnails(\n key,\n metaEntry\n ).length;\n fileCount += thumbCount;\n // Thumbnails are on CDN if original is on CDN\n if (metaEntry.c !== undefined) {\n const entryCdnUrl = cdnUrls[metaEntry.c]?.replace(\n /\\/?$/,\n \"\"\n );\n if (r2PublicUrl && entryCdnUrl === r2PublicUrl) {\n cloudCount += thumbCount;\n } else {\n remoteCount += thumbCount;\n }\n } else {\n localCount += thumbCount;\n }\n }\n }\n } else {\n // Count files from meta for regular folders\n const folderPrefix =\n pathPrefix === \"/\"\n ? `/${entry.name}/`\n : `${pathPrefix}${entry.name}/`;\n for (const k of metaKeys) {\n if (k.startsWith(folderPrefix)) fileCount++;\n }\n // Count cloud vs remote vs local\n const counts = countFileTypes(\n folderPrefix,\n fileEntries,\n cdnUrls,\n r2PublicUrl\n );\n cloudCount = counts.cloudCount;\n remoteCount = counts.remoteCount;\n localCount = counts.localCount;\n updateCount = counts.updateCount;\n }\n\n items.push({\n name: entry.name,\n path: folderPath,\n type: \"folder\",\n fileCount,\n cloudCount,\n remoteCount,\n localCount,\n updateCount,\n isProtected: isImagesFolder,\n });\n }\n }\n }\n } catch {\n // Directory might not exist (all files in cloud)\n }\n\n // Always show images folder at root level if any processed images exist\n if (!relativePath && !seenFolders.has(\"images\")) {\n let thumbnailCount = 0;\n let imgCloudCount = 0;\n let imgRemoteCount = 0;\n let imgLocalCount = 0;\n for (const [key, entry] of fileEntries) {\n if (isProcessed(entry)) {\n const thumbCount = getExistingThumbnails(key, entry).length;\n thumbnailCount += thumbCount;\n // Thumbnails are on CDN if original is on CDN\n if (entry.c !== undefined) {\n const entryCdnUrl = cdnUrls[entry.c]?.replace(/\\/?$/, \"\");\n if (r2PublicUrl && entryCdnUrl === r2PublicUrl) {\n imgCloudCount += thumbCount;\n } else {\n imgRemoteCount += thumbCount;\n }\n } else {\n imgLocalCount += thumbCount;\n }\n }\n }\n if (thumbnailCount > 0) {\n items.push({\n name: \"images\",\n path: \"public/images\",\n type: \"folder\",\n fileCount: thumbnailCount,\n cloudCount: imgCloudCount,\n remoteCount: imgRemoteCount,\n localCount: imgLocalCount,\n isProtected: true,\n });\n }\n }\n\n // If meta is empty and no folders found, return empty with a flag\n if (fileEntries.length === 0 && items.length === 0) {\n return jsonResponse({ items: [], isEmpty: true });\n }\n\n for (const [key, entry] of fileEntries) {\n // Check if this file is under the current path\n if (!key.startsWith(pathPrefix) && pathPrefix !== \"/\") continue;\n if (pathPrefix === \"/\" && !key.startsWith(\"/\")) continue;\n\n // Get the part after the current path\n const remaining =\n pathPrefix === \"/\" ? key.slice(1) : key.slice(pathPrefix.length);\n\n // Skip if empty (shouldn't happen)\n if (!remaining) continue;\n\n // Check if there's a subfolder\n const slashIndex = remaining.indexOf(\"/\");\n\n if (slashIndex !== -1) {\n // This is in a subfolder - show the folder\n const folderName = remaining.slice(0, slashIndex);\n\n if (!seenFolders.has(folderName)) {\n seenFolders.add(folderName);\n\n // Count files in this folder from meta\n const folderPrefix =\n pathPrefix === \"/\"\n ? `/${folderName}/`\n : `${pathPrefix}${folderName}/`;\n let fileCount = 0;\n for (const k of metaKeys) {\n if (k.startsWith(folderPrefix)) fileCount++;\n }\n\n // Count cloud vs remote vs local\n const counts = countFileTypes(\n folderPrefix,\n fileEntries,\n cdnUrls,\n r2PublicUrl\n );\n\n items.push({\n name: folderName,\n path: relativePath\n ? `public/${relativePath}/${folderName}`\n : `public/${folderName}`,\n type: \"folder\",\n fileCount,\n cloudCount: counts.cloudCount,\n remoteCount: counts.remoteCount,\n localCount: counts.localCount,\n updateCount: counts.updateCount,\n isProtected: isInsideImagesFolder,\n });\n }\n } else {\n // This is a file in the current folder\n const fileName = remaining;\n const isImage = isImageFile(fileName);\n const isPushedToCloud = entry.c !== undefined;\n\n // Determine if this is a remote import vs pushed to our R2\n const fileCdnUrl =\n isPushedToCloud && entry.c !== undefined\n ? cdnUrls[entry.c]\n : undefined;\n const normalizedFileCdnUrl = fileCdnUrl?.replace(/\\/$/, \"\") || \"\";\n const isRemote =\n isPushedToCloud &&\n (!r2PublicUrl || normalizedFileCdnUrl !== r2PublicUrl);\n\n let thumbnail: string | undefined;\n let hasThumbnail = false;\n let fileSize: number | undefined;\n\n const entryIsProcessed = isProcessed(entry);\n\n if (isImage && entryIsProcessed) {\n // Has been processed - determine best available thumbnail\n // Prefer sm, then md, then lg, then full, then original\n const hasSm = !!entry.sm;\n const hasMd = !!entry.md;\n const hasLg = !!entry.lg;\n const hasFull = !!entry.f;\n\n let thumbSize: \"sm\" | \"md\" | \"lg\" | \"full\" | null = null;\n if (hasSm) thumbSize = \"sm\";\n else if (hasMd) thumbSize = \"md\";\n else if (hasLg) thumbSize = \"lg\";\n else if (hasFull) thumbSize = \"full\";\n\n if (thumbSize) {\n const thumbPath = getThumbnailPath(key, thumbSize);\n\n if (isPushedToCloud && entry.c !== undefined) {\n // CDN thumbnail - get URL from _cdns array\n const cdnUrl = cdnUrls[entry.c];\n if (cdnUrl) {\n thumbnail = `${cdnUrl}${thumbPath}`;\n hasThumbnail = true;\n }\n } else {\n // Local thumbnail - check if exists\n const localThumbPath = getPublicPath(thumbPath);\n try {\n await fs.access(localThumbPath);\n thumbnail = thumbPath;\n hasThumbnail = true;\n } catch {\n // Thumbnail doesn't exist yet, use original\n thumbnail = key;\n hasThumbnail = false;\n }\n }\n } else {\n // No thumbnails available, use original\n if (isPushedToCloud && entry.c !== undefined) {\n const cdnUrl = cdnUrls[entry.c];\n thumbnail = cdnUrl ? `${cdnUrl}${key}` : key;\n } else {\n thumbnail = key;\n }\n hasThumbnail = false;\n }\n } else if (isImage) {\n // Not processed yet - use original (from CDN if available)\n if (isPushedToCloud && entry.c !== undefined) {\n const cdnUrl = cdnUrls[entry.c];\n thumbnail = cdnUrl ? `${cdnUrl}${key}` : key;\n } else {\n thumbnail = key;\n }\n hasThumbnail = false;\n }\n\n // Try to get file size if file exists locally\n if (!isPushedToCloud) {\n try {\n const filePath = getPublicPath(key);\n const stats = await fs.stat(filePath);\n fileSize = stats.size;\n } catch {\n // File might not exist locally (synced)\n }\n }\n\n items.push({\n name: fileName,\n path: relativePath\n ? `public/${relativePath}/${fileName}`\n : `public/${fileName}`,\n type: \"file\",\n size: fileSize,\n thumbnail,\n hasThumbnail,\n isProcessed: entryIsProcessed,\n hasSm: !!entry.sm,\n hasMd: !!entry.md,\n hasLg: !!entry.lg,\n hasFull: !!entry.f,\n cdnPushed: isPushedToCloud,\n cdnBaseUrl: fileCdnUrl,\n isCloud: isPushedToCloud && !isRemote,\n isRemote,\n isProtected: isInsideImagesFolder,\n dimensions: entry.o\n ? { width: entry.o.w, height: entry.o.h }\n : undefined,\n hasUpdate: entry.u === 1,\n });\n }\n }\n\n return jsonResponse({ items });\n } catch (error) {\n console.error(\"Failed to list directory:\", error);\n return jsonResponse({ error: \"Failed to list directory\" }, { status: 500 });\n }\n}\n\nexport async function handleSearch(request: Request) {\n const searchParams = new URL(request.url).searchParams;\n const query = searchParams.get(\"q\")?.toLowerCase() || \"\";\n const requestedPath = searchParams.get(\"path\") || \"public\";\n\n if (query.length < 2) {\n return jsonResponse({ items: [] });\n }\n\n // Convert path to filter prefix (e.g. \"public/images\" -> \"/images/\")\n const pathPrefix =\n requestedPath === \"public\"\n ? \"/\"\n : \"/\" + requestedPath.replace(/^public\\/?/, \"\");\n const normalizedPrefix = pathPrefix.endsWith(\"/\")\n ? pathPrefix\n : pathPrefix + \"/\";\n\n try {\n const meta = await loadMeta();\n const fileEntries = getFileEntries(meta);\n const cdnUrls = getCdnUrls(meta);\n const r2PublicUrl =\n process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/$/, \"\") || \"\";\n const items: FileItem[] = [];\n\n for (const [key, entry] of fileEntries) {\n // Check if file is within the requested path (including subfolders)\n if (!key.startsWith(normalizedPrefix)) continue;\n\n // Check if the path matches the query\n if (!key.toLowerCase().includes(query)) continue;\n\n const fileName = path.basename(key);\n const relativePath = key.slice(1); // Remove leading /\n const isImage = isImageFile(fileName);\n const isPushedToCloud = entry.c !== undefined;\n\n // Determine if this is a remote import vs pushed to our R2\n const fileCdnUrl =\n isPushedToCloud && entry.c !== undefined ? cdnUrls[entry.c] : undefined;\n const normalizedFileCdnUrl = fileCdnUrl?.replace(/\\/$/, \"\") || \"\";\n const isRemote =\n isPushedToCloud &&\n (!r2PublicUrl || normalizedFileCdnUrl !== r2PublicUrl);\n\n let thumbnail: string | undefined;\n let hasThumbnail = false;\n const entryIsProcessed = isProcessed(entry);\n\n if (isImage && entryIsProcessed) {\n // Has been processed - determine best available thumbnail\n // Prefer sm, then md, then lg, then full, then original\n const hasSm = !!entry.sm;\n const hasMd = !!entry.md;\n const hasLg = !!entry.lg;\n const hasFull = !!entry.f;\n\n let thumbSize: \"sm\" | \"md\" | \"lg\" | \"full\" | null = null;\n if (hasSm) thumbSize = \"sm\";\n else if (hasMd) thumbSize = \"md\";\n else if (hasLg) thumbSize = \"lg\";\n else if (hasFull) thumbSize = \"full\";\n\n if (thumbSize) {\n const thumbPath = getThumbnailPath(key, thumbSize);\n\n if (isPushedToCloud && entry.c !== undefined) {\n const cdnUrl = cdnUrls[entry.c];\n if (cdnUrl) {\n thumbnail = `${cdnUrl}${thumbPath}`;\n hasThumbnail = true;\n }\n } else {\n const localThumbPath = getPublicPath(thumbPath);\n try {\n await fs.access(localThumbPath);\n thumbnail = thumbPath;\n hasThumbnail = true;\n } catch {\n thumbnail = key;\n hasThumbnail = false;\n }\n }\n } else {\n // No thumbnails available, use original\n if (isPushedToCloud && entry.c !== undefined) {\n const cdnUrl = cdnUrls[entry.c];\n thumbnail = cdnUrl ? `${cdnUrl}${key}` : key;\n } else {\n thumbnail = key;\n }\n hasThumbnail = false;\n }\n } else if (isImage) {\n // Not processed yet - use original (from CDN if available)\n if (isPushedToCloud && entry.c !== undefined) {\n const cdnUrl = cdnUrls[entry.c];\n thumbnail = cdnUrl ? `${cdnUrl}${key}` : key;\n } else {\n thumbnail = key;\n }\n hasThumbnail = false;\n }\n\n items.push({\n name: fileName,\n path: `public/${relativePath}`,\n type: \"file\",\n thumbnail,\n hasThumbnail,\n isProcessed: entryIsProcessed,\n hasSm: !!entry.sm,\n hasMd: !!entry.md,\n hasLg: !!entry.lg,\n hasFull: !!entry.f,\n cdnPushed: isPushedToCloud,\n cdnBaseUrl: fileCdnUrl,\n isCloud: isPushedToCloud && !isRemote,\n isRemote,\n dimensions: entry.o\n ? { width: entry.o.w, height: entry.o.h }\n : undefined,\n hasUpdate: entry.u === 1,\n });\n }\n\n return jsonResponse({ items });\n } catch (error) {\n console.error(\"Failed to search:\", error);\n return jsonResponse({ error: \"Failed to search\" }, { status: 500 });\n }\n}\n\nexport async function handleListFolders() {\n try {\n const meta = await loadMeta();\n const fileEntries = getFileEntries(meta);\n const folderSet = new Set<string>();\n\n // Extract all folder paths from meta keys\n for (const [key] of fileEntries) {\n const parts = key.split(\"/\");\n // Build up folder paths: /photos/2024/image.jpg -> photos, photos/2024\n let current = \"\";\n for (let i = 1; i < parts.length - 1; i++) {\n current = current ? `${current}/${parts[i]}` : parts[i];\n folderSet.add(current);\n }\n }\n\n // Also scan filesystem recursively for folders (including empty ones)\n async function scanDir(dir: string, relativePath: string): Promise<void> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (\n entry.isDirectory() &&\n !entry.name.startsWith(\".\") &&\n entry.name !== \"images\"\n ) {\n const folderRelPath = relativePath\n ? `${relativePath}/${entry.name}`\n : entry.name;\n folderSet.add(folderRelPath);\n // Recurse into subdirectory\n await scanDir(path.join(dir, entry.name), folderRelPath);\n }\n }\n } catch {\n // Directory might not exist\n }\n }\n\n const publicDir = getPublicPath();\n await scanDir(publicDir, \"\");\n\n const folders: { path: string; name: string; depth: number }[] = [];\n folders.push({ path: \"public\", name: \"public\", depth: 0 });\n\n const sortedFolders = Array.from(folderSet).sort();\n for (const folderPath of sortedFolders) {\n const depth = folderPath.split(\"/\").length;\n const name = folderPath.split(\"/\").pop() || folderPath;\n folders.push({\n path: `public/${folderPath}`,\n name,\n depth,\n });\n }\n\n return jsonResponse({ folders });\n } catch (error) {\n console.error(\"Failed to list folders:\", error);\n return jsonResponse({ error: \"Failed to list folders\" }, { status: 500 });\n }\n}\n\nexport async function handleCountImages() {\n try {\n const meta = await loadMeta();\n const fileEntries = getFileEntries(meta);\n const allImages: string[] = [];\n\n for (const [key] of fileEntries) {\n const fileName = path.basename(key);\n if (isImageFile(fileName)) {\n allImages.push(key.slice(1)); // Remove leading /\n }\n }\n\n return jsonResponse({\n count: allImages.length,\n images: allImages,\n });\n } catch (error) {\n console.error(\"Failed to count images:\", error);\n return jsonResponse({ error: \"Failed to count images\" }, { status: 500 });\n }\n}\n\nexport async function handleFolderImages(request: Request) {\n try {\n const searchParams = new URL(request.url).searchParams;\n const foldersParam = searchParams.get(\"folders\");\n\n if (!foldersParam) {\n return jsonResponse({ error: \"No folders provided\" }, { status: 400 });\n }\n\n const folders = foldersParam.split(\",\");\n const meta = await loadMeta();\n const fileEntries = getFileEntries(meta);\n const allFiles: string[] = [];\n\n // Convert folder paths to prefixes for matching\n const prefixes = folders.map((f) => {\n const rel = f.replace(/^public\\/?/, \"\");\n return rel ? `/${rel}/` : \"/\";\n });\n\n for (const [key] of fileEntries) {\n // Check if this file is in one of the requested folders\n for (const prefix of prefixes) {\n if (key.startsWith(prefix) || (prefix === \"/\" && key.startsWith(\"/\"))) {\n allFiles.push(key.slice(1)); // Remove leading /\n break;\n }\n }\n }\n\n return jsonResponse({\n count: allFiles.length,\n images: allFiles, // Keep as 'images' for backwards compatibility\n });\n } catch (error) {\n console.error(\"Failed to get folder files:\", error);\n return jsonResponse(\n { error: \"Failed to get folder files\" },\n { status: 500 }\n );\n }\n}\n","import { promises as fs } from 'fs'\nimport path from 'path'\nimport type { FullMeta, MetaEntry } from '../../types'\nimport { getDataPath } from '../../config'\n\nexport async function loadMeta(): Promise<FullMeta> {\n const metaPath = getDataPath('_studio.json')\n \n try {\n const content = await fs.readFile(metaPath, 'utf-8')\n return JSON.parse(content) as FullMeta\n } catch {\n return {}\n }\n}\n\nexport async function saveMeta(meta: FullMeta): Promise<void> {\n const dataDir = getDataPath()\n await fs.mkdir(dataDir, { recursive: true })\n const metaPath = getDataPath('_studio.json')\n \n // Ensure _cdns is at the top by creating ordered object\n const ordered: FullMeta = {}\n if (meta._cdns) {\n ordered._cdns = meta._cdns\n }\n // Add all other entries\n for (const [key, value] of Object.entries(meta)) {\n if (key !== '_cdns') {\n ordered[key] = value\n }\n }\n \n await fs.writeFile(metaPath, JSON.stringify(ordered, null, 2))\n}\n\n/**\n * Get the CDN URLs array from meta\n */\nexport function getCdnUrls(meta: FullMeta): string[] {\n return meta._cdns || []\n}\n\n/**\n * Set the CDN URLs array in meta\n */\nexport function setCdnUrls(meta: FullMeta, urls: string[]): void {\n meta._cdns = urls\n}\n\n/**\n * Get or add a CDN URL, returning its index\n */\nexport function getOrAddCdnIndex(meta: FullMeta, cdnUrl: string): number {\n if (!meta._cdns) {\n meta._cdns = []\n }\n \n // Normalize URL (remove trailing slash)\n const normalizedUrl = cdnUrl.replace(/\\/$/, '')\n \n const existingIndex = meta._cdns.indexOf(normalizedUrl)\n if (existingIndex >= 0) {\n return existingIndex\n }\n \n // Add new CDN URL\n meta._cdns.push(normalizedUrl)\n return meta._cdns.length - 1\n}\n\n/**\n * Get a meta entry (excludes special keys like _cdns)\n */\nexport function getMetaEntry(meta: FullMeta, key: string): MetaEntry | undefined {\n if (key.startsWith('_')) return undefined\n const value = meta[key]\n if (Array.isArray(value)) return undefined\n return value as MetaEntry | undefined\n}\n\n/**\n * Set a meta entry\n */\nexport function setMetaEntry(meta: FullMeta, key: string, entry: MetaEntry): void {\n meta[key] = entry\n}\n\n/**\n * Delete a meta entry\n */\nexport function deleteMetaEntry(meta: FullMeta, key: string): void {\n delete meta[key]\n}\n\n/**\n * Get all file entries (excludes special keys like _cdns)\n */\nexport function getFileEntries(meta: FullMeta): Array<[string, MetaEntry]> {\n return Object.entries(meta).filter(\n ([key, value]) => !key.startsWith('_') && !Array.isArray(value)\n ) as Array<[string, MetaEntry]>\n}\n","import path from 'path'\n\nlet workspacePath: string | null = null\n\n/**\n * Get the workspace root path.\n * In standalone mode, this comes from STUDIO_WORKSPACE env var.\n * In embedded mode, this defaults to process.cwd().\n */\nexport function getWorkspace(): string {\n if (workspacePath === null) {\n workspacePath = process.env.STUDIO_WORKSPACE || process.cwd()\n }\n return workspacePath\n}\n\n/**\n * Get an absolute path within the public folder.\n */\nexport function getPublicPath(...segments: string[]): string {\n return path.join(getWorkspace(), 'public', ...segments)\n}\n\n/**\n * Get an absolute path within the _data folder.\n */\nexport function getDataPath(...segments: string[]): string {\n return path.join(getWorkspace(), '_data', ...segments)\n}\n\n/**\n * Get an absolute path within the src/app folder.\n */\nexport function getSrcAppPath(...segments: string[]): string {\n return path.join(getWorkspace(), 'src', 'app', ...segments)\n}\n\n/**\n * Get an absolute path within the workspace root.\n */\nexport function getWorkspacePath(...segments: string[]): string {\n return path.join(getWorkspace(), ...segments)\n}\n","import { promises as fs } from 'fs'\nimport path from 'path'\n\n/**\n * Convert a filename to a slug-friendly format:\n * - lowercase\n * - spaces and underscores to hyphens\n * - remove special characters except hyphens and dots\n * - collapse multiple hyphens\n * - preserve file extension\n */\nexport function slugifyFilename(filename: string): string {\n const ext = path.extname(filename).toLowerCase()\n const baseName = path.basename(filename, path.extname(filename))\n \n const slugged = baseName\n .toLowerCase()\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '') // Remove diacritics\n .replace(/[_\\s]+/g, '-') // Replace spaces and underscores with hyphens\n .replace(/[^a-z0-9-]/g, '') // Remove non-alphanumeric except hyphens\n .replace(/-+/g, '-') // Collapse multiple hyphens\n .replace(/^-|-$/g, '') // Trim leading/trailing hyphens\n \n // If the slug is empty after processing, use a fallback\n const finalSlug = slugged || 'file'\n \n return finalSlug + ext\n}\n\n/**\n * Slugify a folder name (no extension handling)\n */\nexport function slugifyFolderName(name: string): string {\n const slugged = name\n .toLowerCase()\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '') // Remove diacritics\n .replace(/[_\\s]+/g, '-') // Replace spaces and underscores with hyphens\n .replace(/[^a-z0-9-]/g, '') // Remove non-alphanumeric except hyphens\n .replace(/-+/g, '-') // Collapse multiple hyphens\n .replace(/^-|-$/g, '') // Trim leading/trailing hyphens\n \n return slugged || 'folder'\n}\n\nexport function isImageFile(filename: string): boolean {\n const ext = path.extname(filename).toLowerCase()\n return ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.ico', '.bmp', '.tiff', '.tif'].includes(ext)\n}\n\nexport function isMediaFile(filename: string): boolean {\n const ext = path.extname(filename).toLowerCase()\n // Images\n if (['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.ico', '.bmp', '.tiff', '.tif'].includes(ext)) return true\n // Videos\n if (['.mp4', '.webm', '.mov', '.avi', '.mkv', '.m4v'].includes(ext)) return true\n // Audio\n if (['.mp3', '.wav', '.ogg', '.m4a', '.flac', '.aac'].includes(ext)) return true\n // Documents\n if (['.pdf', '.json'].includes(ext)) return true\n return false\n}\n\nexport function getContentType(filePath: string): string {\n const ext = path.extname(filePath).toLowerCase()\n switch (ext) {\n case '.jpg':\n case '.jpeg':\n return 'image/jpeg'\n case '.png':\n return 'image/png'\n case '.gif':\n return 'image/gif'\n case '.webp':\n return 'image/webp'\n case '.svg':\n return 'image/svg+xml'\n default:\n return 'application/octet-stream'\n }\n}\n\nexport async function getFolderStats(folderPath: string): Promise<{ fileCount: number; totalSize: number }> {\n let fileCount = 0\n let totalSize = 0\n\n async function scanFolder(dir: string): Promise<void> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true })\n for (const entry of entries) {\n if (entry.name.startsWith('.')) continue\n const fullPath = path.join(dir, entry.name)\n if (entry.isDirectory()) {\n await scanFolder(fullPath)\n } else if (isMediaFile(entry.name)) {\n fileCount++\n const stats = await fs.stat(fullPath)\n totalSize += stats.size\n }\n }\n } catch { /* ignore errors */ }\n }\n\n await scanFolder(folderPath)\n return { fileCount, totalSize }\n}\n","import { promises as fs } from \"fs\";\nimport path from \"path\";\nimport sharp from \"sharp\";\nimport type { MetaEntry, Dimensions } from \"../../types\";\nimport { getPublicPath } from \"../../config\";\n\nexport const FULL_MAX_WIDTH = 2560;\n\nexport const DEFAULT_SIZES: Record<\n string,\n { width: number; suffix: string; key: \"sm\" | \"md\" | \"lg\" }\n> = {\n small: { width: 300, suffix: \"-sm\", key: \"sm\" },\n medium: { width: 700, suffix: \"-md\", key: \"md\" },\n large: { width: 1400, suffix: \"-lg\", key: \"lg\" },\n};\n\nexport async function processImage(\n buffer: Buffer,\n imageKey: string\n): Promise<MetaEntry> {\n // Apply EXIF rotation first to get correct dimensions\n // Many cameras store images rotated with EXIF metadata to display correctly\n const rotatedBuffer = await sharp(buffer).rotate().toBuffer();\n const metadata = await sharp(rotatedBuffer).metadata();\n const originalWidth = metadata.width || 0;\n const originalHeight = metadata.height || 0;\n const ratio = originalHeight / originalWidth;\n\n // Remove leading slash for path operations\n const keyWithoutSlash = imageKey.startsWith(\"/\")\n ? imageKey.slice(1)\n : imageKey;\n const baseName = path.basename(\n keyWithoutSlash,\n path.extname(keyWithoutSlash)\n );\n const ext = path.extname(keyWithoutSlash).toLowerCase();\n const imageDir = path.dirname(keyWithoutSlash);\n\n const imagesPath = getPublicPath(\"images\", imageDir === \".\" ? \"\" : imageDir);\n await fs.mkdir(imagesPath, { recursive: true });\n\n const isPng = ext === \".png\";\n const outputExt = isPng ? \".png\" : \".jpg\";\n\n // Build the result entry\n const entry: MetaEntry = {\n o: { w: originalWidth, h: originalHeight },\n };\n\n // Generate full size (capped at FULL_MAX_WIDTH)\n const fullFileName =\n imageDir === \".\"\n ? `${baseName}${outputExt}`\n : `${imageDir}/${baseName}${outputExt}`;\n const fullPath = getPublicPath(\"images\", fullFileName);\n\n let fullWidth = originalWidth;\n let fullHeight = originalHeight;\n\n if (originalWidth > FULL_MAX_WIDTH) {\n fullWidth = FULL_MAX_WIDTH;\n fullHeight = Math.round(FULL_MAX_WIDTH * ratio);\n if (isPng) {\n await sharp(rotatedBuffer)\n .resize(fullWidth, fullHeight)\n .png({ quality: 85 })\n .toFile(fullPath);\n } else {\n await sharp(rotatedBuffer)\n .resize(fullWidth, fullHeight)\n .jpeg({ quality: 85 })\n .toFile(fullPath);\n }\n } else {\n if (isPng) {\n await sharp(rotatedBuffer).png({ quality: 85 }).toFile(fullPath);\n } else {\n await sharp(rotatedBuffer).jpeg({ quality: 85 }).toFile(fullPath);\n }\n }\n entry.f = { w: fullWidth, h: fullHeight };\n\n // Generate thumbnail sizes\n for (const [, sizeConfig] of Object.entries(DEFAULT_SIZES)) {\n const { width: maxWidth, suffix, key } = sizeConfig;\n if (originalWidth <= maxWidth) {\n continue; // Skip if original is smaller than this size\n }\n\n const newHeight = Math.round(maxWidth * ratio);\n const sizeFileName = `${baseName}${suffix}${outputExt}`;\n const sizeFilePath =\n imageDir === \".\" ? sizeFileName : `${imageDir}/${sizeFileName}`;\n const sizePath = getPublicPath(\"images\", sizeFilePath);\n\n if (isPng) {\n await sharp(rotatedBuffer)\n .resize(maxWidth, newHeight)\n .png({ quality: 80 })\n .toFile(sizePath);\n } else {\n await sharp(rotatedBuffer)\n .resize(maxWidth, newHeight)\n .jpeg({ quality: 80 })\n .toFile(sizePath);\n }\n\n entry[key] = { w: maxWidth, h: newHeight };\n }\n\n return entry;\n}\n","import { promises as fs } from 'fs'\nimport { S3Client, GetObjectCommand, PutObjectCommand, DeleteObjectCommand, CopyObjectCommand } from '@aws-sdk/client-s3'\nimport { getAllThumbnailPaths } from '../../types'\nimport { getContentType } from './files'\nimport { getPublicPath } from '../../config'\n\nfunction getR2Client() {\n const accountId = process.env.CLOUDFLARE_R2_ACCOUNT_ID\n const accessKeyId = process.env.CLOUDFLARE_R2_ACCESS_KEY_ID\n const secretAccessKey = process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY\n\n if (!accountId || !accessKeyId || !secretAccessKey) {\n throw new Error('R2 not configured')\n }\n\n return new S3Client({\n region: 'auto',\n endpoint: `https://${accountId}.r2.cloudflarestorage.com`,\n credentials: { accessKeyId, secretAccessKey },\n })\n}\n\nexport async function downloadFromCdn(originalPath: string): Promise<Buffer> {\n const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME\n if (!bucketName) throw new Error('R2 bucket not configured')\n\n const r2 = getR2Client()\n const maxRetries = 3\n let lastError: Error | undefined\n\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n const response = await r2.send(\n new GetObjectCommand({\n Bucket: bucketName,\n Key: originalPath.replace(/^\\//, ''),\n })\n )\n\n const stream = response.Body as NodeJS.ReadableStream\n const chunks: Buffer[] = []\n for await (const chunk of stream) {\n chunks.push(Buffer.from(chunk))\n }\n return Buffer.concat(chunks)\n } catch (error) {\n lastError = error as Error\n // Wait before retry (exponential backoff: 500ms, 1s)\n if (attempt < maxRetries - 1) {\n await new Promise(resolve => setTimeout(resolve, 500 * (attempt + 1)))\n }\n }\n }\n\n throw lastError || new Error(`Failed to download ${originalPath} after ${maxRetries} attempts`)\n}\n\nexport async function uploadToCdn(imageKey: string): Promise<void> {\n const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME\n if (!bucketName) throw new Error('R2 bucket not configured')\n\n const r2 = getR2Client()\n\n // Upload all thumbnail sizes derived from imageKey\n for (const thumbPath of getAllThumbnailPaths(imageKey)) {\n const localPath = getPublicPath(thumbPath)\n try {\n const fileBuffer = await fs.readFile(localPath)\n await r2.send(\n new PutObjectCommand({\n Bucket: bucketName,\n Key: thumbPath.replace(/^\\//, ''),\n Body: fileBuffer,\n ContentType: getContentType(thumbPath),\n })\n )\n } catch {\n // File might not exist (e.g., if image is smaller than thumbnail size)\n }\n }\n}\n\nexport async function deleteLocalThumbnails(imageKey: string): Promise<void> {\n for (const thumbPath of getAllThumbnailPaths(imageKey)) {\n const localPath = getPublicPath(thumbPath)\n try {\n await fs.unlink(localPath)\n } catch {\n // File might not exist\n }\n }\n}\n\n/**\n * Download image from a remote URL (not R2)\n */\nexport async function downloadFromRemoteUrl(url: string): Promise<Buffer> {\n const maxRetries = 3\n let lastError: Error | undefined\n\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n const response = await fetch(url)\n if (!response.ok) {\n throw new Error(`Failed to download from ${url}: ${response.status}`)\n }\n const arrayBuffer = await response.arrayBuffer()\n return Buffer.from(arrayBuffer)\n } catch (error) {\n lastError = error as Error\n // Wait before retry (exponential backoff: 500ms, 1s)\n if (attempt < maxRetries - 1) {\n await new Promise(resolve => setTimeout(resolve, 500 * (attempt + 1)))\n }\n }\n }\n\n throw lastError || new Error(`Failed to download from ${url} after ${maxRetries} attempts`)\n}\n\n/**\n * Upload original image to R2 CDN\n */\nexport async function uploadOriginalToCdn(imageKey: string): Promise<void> {\n const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME\n if (!bucketName) throw new Error('R2 bucket not configured')\n\n const r2 = getR2Client()\n const localPath = getPublicPath(imageKey)\n const fileBuffer = await fs.readFile(localPath)\n \n await r2.send(\n new PutObjectCommand({\n Bucket: bucketName,\n Key: imageKey.replace(/^\\//, ''),\n Body: fileBuffer,\n ContentType: getContentType(imageKey),\n })\n )\n}\n\n/**\n * Delete original and thumbnails from R2 CDN\n */\nexport async function deleteFromCdn(imageKey: string, hasThumbnails: boolean): Promise<void> {\n const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME\n if (!bucketName) throw new Error('R2 bucket not configured')\n\n const r2 = getR2Client()\n\n // Delete original\n try {\n await r2.send(\n new DeleteObjectCommand({\n Bucket: bucketName,\n Key: imageKey.replace(/^\\//, ''),\n })\n )\n } catch {\n // May not exist\n }\n\n // Delete thumbnails if they exist\n if (hasThumbnails) {\n for (const thumbPath of getAllThumbnailPaths(imageKey)) {\n try {\n await r2.send(\n new DeleteObjectCommand({\n Bucket: bucketName,\n Key: thumbPath.replace(/^\\//, ''),\n })\n )\n } catch {\n // May not exist\n }\n }\n }\n}\n\n/**\n * Delete only thumbnails from R2 CDN (keeps original)\n */\nexport async function deleteThumbnailsFromCdn(imageKey: string): Promise<void> {\n const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME\n if (!bucketName) throw new Error('R2 bucket not configured')\n\n const r2 = getR2Client()\n\n for (const thumbPath of getAllThumbnailPaths(imageKey)) {\n try {\n await r2.send(\n new DeleteObjectCommand({\n Bucket: bucketName,\n Key: thumbPath.replace(/^\\//, ''),\n })\n )\n } catch {\n // May not exist\n }\n }\n}\n\n/**\n * Delete only original from R2 CDN (keeps thumbnails)\n * Used for cache busting before re-upload\n */\nexport async function deleteOriginalFromCdn(imageKey: string): Promise<void> {\n const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME\n if (!bucketName) throw new Error('R2 bucket not configured')\n\n const r2 = getR2Client()\n\n try {\n await r2.send(\n new DeleteObjectCommand({\n Bucket: bucketName,\n Key: imageKey.replace(/^\\//, ''),\n })\n )\n } catch {\n // May not exist\n }\n}\n\n/**\n * Copy a file within R2 CDN (server-side, no download/upload)\n * Used for rename/move operations - much faster than download+upload\n */\nexport async function copyInCdn(oldKey: string, newKey: string): Promise<void> {\n const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME\n if (!bucketName) throw new Error('R2 bucket not configured')\n\n const r2 = getR2Client()\n const oldKeyClean = oldKey.replace(/^\\//, '')\n const newKeyClean = newKey.replace(/^\\//, '')\n\n await r2.send(\n new CopyObjectCommand({\n Bucket: bucketName,\n CopySource: `${bucketName}/${oldKeyClean}`,\n Key: newKeyClean,\n })\n )\n}\n\n/**\n * Move a file within R2 CDN (copy + delete, server-side)\n * Handles original and thumbnails\n */\nexport async function moveInCdn(oldKey: string, newKey: string, hasThumbnails: boolean): Promise<void> {\n // Copy original\n await copyInCdn(oldKey, newKey)\n \n // Copy thumbnails if they exist\n if (hasThumbnails) {\n const oldThumbPaths = getAllThumbnailPaths(oldKey)\n const newThumbPaths = getAllThumbnailPaths(newKey)\n \n for (let i = 0; i < oldThumbPaths.length; i++) {\n try {\n await copyInCdn(oldThumbPaths[i], newThumbPaths[i])\n } catch {\n // Thumbnail might not exist\n }\n }\n }\n \n // Delete old files\n await deleteFromCdn(oldKey, hasThumbnails)\n}\n","/**\n * Dimensions object {w, h}\n */\nexport interface Dimensions {\n w: number;\n h: number;\n}\n\n/**\n * Meta entry - works for images and non-images\n * o: original dimensions, c: CDN index\n * sm/md/lg/f: thumbnail dimensions (presence implies processed)\n */\nexport interface MetaEntry {\n o?: Dimensions; // original dimensions {w, h}\n sm?: Dimensions; // small thumbnail (300px width)\n md?: Dimensions; // medium thumbnail (700px width)\n lg?: Dimensions; // large thumbnail (1400px width)\n f?: Dimensions; // full size (capped at 2560px width)\n c?: number; // CDN index - index into _cdns array\n u?: 1; // update pending - local file overrides cloud file\n b?: string; // blur hash or base64 placeholder\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\n * Example: { \"/portfolio/photo.jpg\": { o: {w:2400,h:1600}, sm: {w:300,h:200}, ... } }\n */\nexport type LeanMeta = Record<string, MetaEntry>;\n\n// 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 isCloud?: boolean; // true if file exists in cloud (CDN pushed or remote)\n isProtected?: boolean; // true for images folder and its contents (cannot select/modify)\n // Folder-specific properties\n fileCount?: number;\n totalSize?: number;\n cloudCount?: number; // Number of R2 cloud files in folder\n remoteCount?: number; // Number of remote (imported URL) files in folder\n localCount?: number; // Number of local files in folder\n // For showing thumbnails - path to -sm version if exists\n thumbnail?: string;\n // Whether a processed thumbnail exists\n hasThumbnail?: boolean;\n // Which thumbnail sizes exist\n hasSm?: boolean;\n hasMd?: boolean;\n hasLg?: boolean;\n hasFull?: boolean;\n // Update pending - local file overrides cloud file\n hasUpdate?: boolean;\n // Number of files with pending updates in folder\n updateCount?: number;\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(\n originalPath: string,\n size: \"sm\" | \"md\" | \"lg\" | \"full\"\n): 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\n/**\n * Check if an image entry is processed (has any thumbnail dimensions)\n */\nexport function isProcessed(entry: MetaEntry | undefined): boolean {\n if (!entry) return false;\n return !!(entry.f || entry.lg || entry.md || entry.sm);\n}\n","/**\n * Utility functions to replace NextResponse with standard Response\n * This allows handlers to work without Next.js dependency\n */\n\n/**\n * Create a JSON response (mimics NextResponse.json)\n */\nexport function jsonResponse<T>(\n data: T,\n init?: { status?: number; headers?: Record<string, string> }\n): Response {\n const headers = new Headers({\n 'Content-Type': 'application/json',\n ...init?.headers,\n })\n\n return new Response(JSON.stringify(data), {\n status: init?.status ?? 200,\n headers,\n })\n}\n\n/**\n * Create a streaming response for Server-Sent Events\n */\nexport function streamResponse(\n stream: ReadableStream,\n init?: { headers?: Record<string, string> }\n): Response {\n const headers = new Headers({\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n ...init?.headers,\n })\n\n return new Response(stream, {\n status: 200,\n headers,\n })\n}\n\n/**\n * Create an SSE encoder for streaming responses\n */\nexport function createSSEStream() {\n const encoder = new TextEncoder()\n let controller: ReadableStreamDefaultController<Uint8Array> | null = null\n\n const stream = new ReadableStream<Uint8Array>({\n start(c) {\n controller = c\n },\n })\n\n return {\n stream,\n send(event: string, data: unknown) {\n if (controller) {\n const message = `event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`\n controller.enqueue(encoder.encode(message))\n }\n },\n close() {\n if (controller) {\n controller.close()\n }\n },\n }\n}\n","import { promises as fs } from \"fs\";\nimport path from \"path\";\nimport sharp from \"sharp\";\nimport type { MetaEntry } from \"../types\";\nimport { getAllThumbnailPaths, isProcessed } from \"../types\";\nimport {\n loadMeta,\n saveMeta,\n isImageFile,\n isMediaFile,\n getCdnUrls,\n downloadFromCdn,\n downloadFromRemoteUrl,\n uploadOriginalToCdn,\n uploadToCdn,\n deleteFromCdn,\n deleteLocalThumbnails,\n processImage,\n slugifyFilename,\n slugifyFolderName,\n moveInCdn,\n} from \"./utils\";\nimport { getPublicPath, getWorkspacePath } from \"../config\";\nimport {\n jsonResponse,\n streamResponse,\n createSSEStream,\n} from \"./utils/response\";\nimport { deleteEmptyFolders } from \"./utils/folders\";\nimport { isOperationCancelled, clearCancelledOperation } from \"./images\";\n\nexport async function handleUpload(request: Request) {\n try {\n const formData = await request.formData();\n const file = formData.get(\"file\") as File | null;\n const targetPath = (formData.get(\"path\") as string) || \"public\";\n\n if (!file) {\n return jsonResponse({ error: \"No file provided\" }, { status: 400 });\n }\n\n const bytes = await file.arrayBuffer();\n const buffer = Buffer.from(bytes);\n\n // Slugify filename to be URL-safe (lowercase, no spaces, etc.)\n const fileName = slugifyFilename(file.name);\n const ext = path.extname(fileName).toLowerCase();\n\n const isImage = isImageFile(fileName);\n const isMedia = isMediaFile(fileName);\n\n const meta = await loadMeta();\n\n let relativeDir = \"\";\n if (targetPath === \"public\") {\n relativeDir = \"\";\n } else if (targetPath.startsWith(\"public/\")) {\n relativeDir = targetPath.replace(\"public/\", \"\");\n }\n\n if (relativeDir === \"images\" || relativeDir.startsWith(\"images/\")) {\n return jsonResponse(\n {\n error:\n \"Cannot upload to images/ folder. Upload to public/ instead - thumbnails are generated automatically.\",\n },\n { status: 400 }\n );\n }\n\n // Build the meta key\n let imageKey =\n \"/\" + (relativeDir ? `${relativeDir}/${fileName}` : fileName);\n\n // Check for collision - rename if needed\n if (meta[imageKey]) {\n const baseName = path.basename(fileName, ext);\n let counter = 1;\n let newFileName = `${baseName}-${counter}${ext}`;\n let newKey =\n \"/\" + (relativeDir ? `${relativeDir}/${newFileName}` : newFileName);\n\n while (meta[newKey]) {\n counter++;\n newFileName = `${baseName}-${counter}${ext}`;\n newKey =\n \"/\" + (relativeDir ? `${relativeDir}/${newFileName}` : newFileName);\n }\n\n imageKey = newKey;\n }\n\n // Extract actual filename from key\n const actualFileName = path.basename(imageKey);\n\n const uploadDir = getPublicPath(relativeDir);\n await fs.mkdir(uploadDir, { recursive: true });\n await fs.writeFile(path.join(uploadDir, actualFileName), buffer);\n\n if (!isMedia) {\n return jsonResponse({\n success: true,\n message: \"File uploaded (not a media file)\",\n path: `public/${relativeDir ? relativeDir + \"/\" : \"\"}${actualFileName}`,\n });\n }\n\n // Add to meta\n if (isImage && ext !== \".svg\") {\n // Read dimensions for images (apply EXIF rotation to get correct dimensions)\n try {\n const rotatedBuffer = await sharp(buffer).rotate().toBuffer();\n const metadata = await sharp(rotatedBuffer).metadata();\n meta[imageKey] = {\n o: { w: metadata.width || 0, h: metadata.height || 0 },\n };\n } catch {\n meta[imageKey] = { o: { w: 0, h: 0 } };\n }\n } else {\n // Non-image media or SVG\n meta[imageKey] = {};\n }\n\n await saveMeta(meta);\n\n return jsonResponse({\n success: true,\n imageKey,\n message: 'File uploaded. Run \"Process Images\" to generate thumbnails.',\n });\n } catch (error) {\n console.error(\"Failed to upload:\", error);\n const message = error instanceof Error ? error.message : \"Unknown error\";\n return jsonResponse(\n { error: `Failed to upload file: ${message}` },\n { status: 500 }\n );\n }\n}\n\nexport async function handleDelete(request: Request) {\n try {\n const { paths } = (await request.json()) as { paths: string[] };\n\n if (!paths || !Array.isArray(paths) || paths.length === 0) {\n return jsonResponse({ error: \"No paths provided\" }, { status: 400 });\n }\n\n const meta = await loadMeta();\n const deleted: string[] = [];\n const errors: string[] = [];\n const sourceFolders = new Set<string>();\n\n for (const itemPath of paths) {\n try {\n if (!itemPath.startsWith(\"public/\")) {\n errors.push(`Invalid path: ${itemPath}`);\n continue;\n }\n\n const absolutePath = getWorkspacePath(itemPath);\n const imageKey = \"/\" + itemPath.replace(/^public\\//, \"\");\n\n // Track source folder for cleanup\n sourceFolders.add(path.dirname(absolutePath));\n\n // Check if this is in meta (could be synced with no local file)\n const entry = meta[imageKey] as MetaEntry | undefined;\n const isPushedToCloud = entry?.c !== undefined;\n const hasThumbnails = entry ? isProcessed(entry) : false;\n\n // Try to delete local file/folder\n try {\n const stats = await fs.stat(absolutePath);\n\n if (stats.isDirectory()) {\n await fs.rm(absolutePath, { recursive: true });\n\n // Remove all meta entries under this folder\n const prefix = imageKey + \"/\";\n for (const key of Object.keys(meta)) {\n if (key.startsWith(prefix) || key === imageKey) {\n const keyEntry = meta[key] as MetaEntry | undefined;\n const keyHasThumbnails = keyEntry\n ? isProcessed(keyEntry)\n : false;\n\n // Delete from CDN if pushed\n if (keyEntry?.c !== undefined) {\n try {\n await deleteFromCdn(key, keyHasThumbnails);\n } catch {\n /* ignore CDN delete errors */\n }\n } else {\n // Delete local thumbnails if not synced\n for (const thumbPath of getAllThumbnailPaths(key)) {\n const absoluteThumbPath = getPublicPath(thumbPath);\n try {\n await fs.unlink(absoluteThumbPath);\n } catch {\n /* ignore */\n }\n }\n }\n delete meta[key];\n }\n }\n } else {\n await fs.unlink(absolutePath);\n\n const isInImagesFolder = itemPath.startsWith(\"public/images/\");\n\n if (!isInImagesFolder && entry) {\n // Delete from CDN if pushed\n if (isPushedToCloud) {\n try {\n await deleteFromCdn(imageKey, hasThumbnails);\n } catch {\n /* ignore CDN delete errors */\n }\n } else {\n // Delete local thumbnails if not synced\n for (const thumbPath of getAllThumbnailPaths(imageKey)) {\n const absoluteThumbPath = getPublicPath(thumbPath);\n try {\n await fs.unlink(absoluteThumbPath);\n } catch {\n /* ignore */\n }\n }\n }\n delete meta[imageKey];\n }\n }\n } catch {\n // File doesn't exist locally - might be synced\n if (entry) {\n // Delete from CDN if pushed\n if (isPushedToCloud) {\n try {\n await deleteFromCdn(imageKey, hasThumbnails);\n } catch {\n /* ignore CDN delete errors */\n }\n }\n delete meta[imageKey];\n } else {\n // Check if it's a folder prefix in meta\n const prefix = imageKey + \"/\";\n let foundAny = false;\n for (const key of Object.keys(meta)) {\n if (key.startsWith(prefix)) {\n const keyEntry = meta[key] as MetaEntry | undefined;\n const keyHasThumbnails = keyEntry\n ? isProcessed(keyEntry)\n : false;\n // Delete from CDN if pushed\n if (keyEntry?.c !== undefined) {\n try {\n await deleteFromCdn(key, keyHasThumbnails);\n } catch {\n /* ignore CDN delete errors */\n }\n }\n delete meta[key];\n foundAny = true;\n }\n }\n if (!foundAny) {\n errors.push(`Not found: ${itemPath}`);\n continue;\n }\n }\n }\n\n deleted.push(itemPath);\n } catch (error) {\n console.error(`Failed to delete ${itemPath}:`, error);\n errors.push(itemPath);\n }\n }\n\n await saveMeta(meta);\n\n // Clean up empty source folders\n for (const folder of sourceFolders) {\n await deleteEmptyFolders(folder);\n }\n\n return jsonResponse({\n success: true,\n deleted,\n errors: errors.length > 0 ? errors : undefined,\n });\n } catch (error) {\n console.error(\"Failed to delete:\", error);\n return jsonResponse({ error: \"Failed to delete files\" }, { status: 500 });\n }\n}\n\n/**\n * Delete files/folders with streaming progress\n */\nexport async function handleDeleteStream(request: Request) {\n const encoder = new TextEncoder();\n\n const stream = new ReadableStream({\n async start(controller) {\n const sendEvent = (data: object) => {\n try {\n controller.enqueue(\n encoder.encode(`data: ${JSON.stringify(data)}\\n\\n`)\n );\n } catch {\n // Controller may be closed\n }\n };\n\n try {\n const { paths, operationId } = (await request.json()) as {\n paths: string[];\n operationId?: string;\n };\n\n if (!paths || !Array.isArray(paths) || paths.length === 0) {\n sendEvent({ type: \"error\", message: \"No paths provided\" });\n controller.close();\n return;\n }\n\n // Helper to check if operation was cancelled\n const isCancelled = () =>\n operationId ? isOperationCancelled(operationId) : false;\n\n const meta = await loadMeta();\n const deleted: string[] = [];\n const errors: string[] = [];\n const sourceFolders = new Set<string>();\n const total = paths.length;\n\n sendEvent({ type: \"start\", total });\n\n for (let i = 0; i < paths.length; i++) {\n // Check for cancellation before each item\n if (isCancelled()) {\n await saveMeta(meta);\n // Clean up empty folders\n for (const folder of sourceFolders) {\n await deleteEmptyFolders(folder);\n }\n if (operationId) clearCancelledOperation(operationId);\n sendEvent({\n type: \"complete\",\n deleted: deleted.length,\n errors: errors.length,\n message: `Stopped. Deleted ${deleted.length} item${\n deleted.length !== 1 ? \"s\" : \"\"\n }.`,\n cancelled: true,\n });\n controller.close();\n return;\n }\n\n const itemPath = paths[i];\n\n try {\n if (!itemPath.startsWith(\"public/\")) {\n errors.push(`Invalid path: ${itemPath}`);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n deleted: deleted.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: path.basename(itemPath),\n });\n continue;\n }\n\n const absolutePath = getWorkspacePath(itemPath);\n const imageKey = \"/\" + itemPath.replace(/^public\\//, \"\");\n\n // Track source folder for cleanup\n sourceFolders.add(path.dirname(absolutePath));\n\n // Check if this is in meta (could be synced with no local file)\n const entry = meta[imageKey] as MetaEntry | undefined;\n const isPushedToCloud = entry?.c !== undefined;\n const hasThumbnails = entry ? isProcessed(entry) : false;\n\n // Try to delete local file/folder\n try {\n const stats = await fs.stat(absolutePath);\n\n if (stats.isDirectory()) {\n await fs.rm(absolutePath, { recursive: true });\n\n // Remove all meta entries under this folder\n const prefix = imageKey + \"/\";\n for (const key of Object.keys(meta)) {\n if (key.startsWith(prefix) || key === imageKey) {\n const keyEntry = meta[key] as MetaEntry | undefined;\n const keyHasThumbnails = keyEntry\n ? isProcessed(keyEntry)\n : false;\n\n // Delete from CDN if pushed\n if (keyEntry?.c !== undefined) {\n try {\n await deleteFromCdn(key, keyHasThumbnails);\n } catch {\n /* ignore CDN delete errors */\n }\n } else {\n // Delete local thumbnails if not synced\n for (const thumbPath of getAllThumbnailPaths(key)) {\n const absoluteThumbPath = getPublicPath(thumbPath);\n try {\n await fs.unlink(absoluteThumbPath);\n } catch {\n /* ignore */\n }\n }\n }\n delete meta[key];\n }\n }\n } else {\n await fs.unlink(absolutePath);\n\n const isInImagesFolder = itemPath.startsWith(\"public/images/\");\n\n if (!isInImagesFolder && entry) {\n // Delete from CDN if pushed\n if (isPushedToCloud) {\n try {\n await deleteFromCdn(imageKey, hasThumbnails);\n } catch {\n /* ignore CDN delete errors */\n }\n } else {\n // Delete local thumbnails if not synced\n for (const thumbPath of getAllThumbnailPaths(imageKey)) {\n const absoluteThumbPath = getPublicPath(thumbPath);\n try {\n await fs.unlink(absoluteThumbPath);\n } catch {\n /* ignore */\n }\n }\n }\n delete meta[imageKey];\n }\n }\n } catch {\n // File doesn't exist locally - might be synced\n if (entry) {\n // Delete from CDN if pushed\n if (isPushedToCloud) {\n try {\n await deleteFromCdn(imageKey, hasThumbnails);\n } catch {\n /* ignore CDN delete errors */\n }\n }\n delete meta[imageKey];\n } else {\n // Check if it's a folder prefix in meta\n const prefix = imageKey + \"/\";\n let foundAny = false;\n for (const key of Object.keys(meta)) {\n if (key.startsWith(prefix)) {\n const keyEntry = meta[key] as MetaEntry | undefined;\n const keyHasThumbnails = keyEntry\n ? isProcessed(keyEntry)\n : false;\n // Delete from CDN if pushed\n if (keyEntry?.c !== undefined) {\n try {\n await deleteFromCdn(key, keyHasThumbnails);\n } catch {\n /* ignore CDN delete errors */\n }\n }\n delete meta[key];\n foundAny = true;\n }\n }\n if (!foundAny) {\n errors.push(`Not found: ${itemPath}`);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n deleted: deleted.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: path.basename(itemPath),\n });\n continue;\n }\n }\n }\n\n // Save meta incrementally\n await saveMeta(meta);\n deleted.push(itemPath);\n } catch (error) {\n console.error(`Failed to delete ${itemPath}:`, error);\n errors.push(itemPath);\n }\n\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n deleted: deleted.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: path.basename(itemPath),\n });\n }\n\n // Clean up empty source folders\n for (const folder of sourceFolders) {\n await deleteEmptyFolders(folder);\n }\n\n if (operationId) clearCancelledOperation(operationId);\n sendEvent({\n type: \"complete\",\n deleted: deleted.length,\n errors: errors.length,\n errorMessages: errors.length > 0 ? errors : undefined,\n });\n } catch (error) {\n console.error(\"Failed to delete:\", error);\n sendEvent({ type: \"error\", message: \"Failed to delete files\" });\n } finally {\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n}\n\nexport async function handleCreateFolder(request: Request) {\n try {\n const { parentPath, name } = await request.json();\n\n if (!name || typeof name !== \"string\") {\n return jsonResponse(\n { error: \"Folder name is required\" },\n { status: 400 }\n );\n }\n\n // Slugify folder name to be URL-safe (lowercase, no spaces, etc.)\n const sanitizedName = slugifyFolderName(name);\n if (!sanitizedName) {\n return jsonResponse({ error: \"Invalid folder name\" }, { status: 400 });\n }\n\n const safePath = (parentPath || \"public\").replace(/\\.\\./g, \"\");\n const folderPath = getWorkspacePath(safePath, sanitizedName);\n\n if (!folderPath.startsWith(getPublicPath())) {\n return jsonResponse({ error: \"Invalid path\" }, { status: 400 });\n }\n\n try {\n await fs.access(folderPath);\n return jsonResponse(\n { error: \"A folder with this name already exists\" },\n { status: 400 }\n );\n } catch {\n // Good - folder doesn't exist\n }\n\n await fs.mkdir(folderPath, { recursive: true });\n\n return jsonResponse({\n success: true,\n path: path.join(safePath, sanitizedName),\n });\n } catch (error) {\n console.error(\"Failed to create folder:\", error);\n return jsonResponse({ error: \"Failed to create folder\" }, { status: 500 });\n }\n}\n\nexport async function handleRename(request: Request) {\n const publicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/$/, \"\");\n\n try {\n const { oldPath, newName } = await request.json();\n\n if (!oldPath || !newName) {\n return jsonResponse(\n { error: \"Path and new name are required\" },\n { status: 400 }\n );\n }\n\n const safePath = oldPath.replace(/\\.\\./g, \"\");\n const absoluteOldPath = getWorkspacePath(safePath);\n\n if (!absoluteOldPath.startsWith(getPublicPath())) {\n return jsonResponse({ error: \"Invalid path\" }, { status: 400 });\n }\n\n const oldRelativePath = safePath.replace(/^public\\//, \"\");\n const oldKey = \"/\" + oldRelativePath;\n const isImage = isImageFile(path.basename(oldPath));\n\n // Load meta to check if this is a cloud file\n const meta = await loadMeta();\n const cdnUrls = getCdnUrls(meta);\n const entry = meta[oldKey] as MetaEntry | undefined;\n const isInCloud = entry?.c !== undefined;\n const fileCdnUrl =\n isInCloud && entry.c !== undefined ? cdnUrls[entry.c] : undefined;\n const isInOurR2 = isInCloud && fileCdnUrl === publicUrl;\n const hasThumbnails = entry ? isProcessed(entry) : false;\n\n // Check if local file exists\n let hasLocalFile = false;\n let isFile = true;\n try {\n const stats = await fs.stat(absoluteOldPath);\n hasLocalFile = true;\n isFile = stats.isFile();\n } catch {\n // No local file - might be cloud-only\n if (!isInCloud) {\n return jsonResponse(\n { error: \"File or folder not found\" },\n { status: 404 }\n );\n }\n }\n\n // Slugify name based on whether it's a file or folder\n const sanitizedName = isFile\n ? slugifyFilename(newName)\n : slugifyFolderName(newName);\n if (!sanitizedName) {\n return jsonResponse({ error: \"Invalid name\" }, { status: 400 });\n }\n\n const parentDir = path.dirname(absoluteOldPath);\n const absoluteNewPath = path.join(parentDir, sanitizedName);\n const newRelativePath = path.join(\n path.dirname(oldRelativePath),\n sanitizedName\n );\n const newKey = \"/\" + newRelativePath;\n\n // Check if new name already exists in meta\n if (meta[newKey]) {\n return jsonResponse(\n { error: \"An item with this name already exists\" },\n { status: 400 }\n );\n }\n\n // Check if new local path already exists\n try {\n await fs.access(absoluteNewPath);\n return jsonResponse(\n { error: \"An item with this name already exists\" },\n { status: 400 }\n );\n } catch {\n // Good - new path doesn't exist\n }\n\n // Handle cloud-only file: use server-side copy in R2\n if (isInOurR2 && !hasLocalFile) {\n // Server-side rename in R2 (copy + delete, no download needed)\n await moveInCdn(oldKey, newKey, hasThumbnails);\n\n // Update meta\n delete meta[oldKey];\n meta[newKey] = entry;\n await saveMeta(meta);\n\n const newPath = path.join(path.dirname(safePath), sanitizedName);\n return jsonResponse({ success: true, newPath });\n }\n\n // Handle local file rename\n if (hasLocalFile) {\n await fs.rename(absoluteOldPath, absoluteNewPath);\n }\n\n if (isImage && entry) {\n const oldThumbPaths = getAllThumbnailPaths(oldKey);\n const newThumbPaths = getAllThumbnailPaths(newKey);\n\n // Rename local thumbnails\n for (let i = 0; i < oldThumbPaths.length; i++) {\n const oldThumbPath = getPublicPath(oldThumbPaths[i]);\n const newThumbPath = getPublicPath(newThumbPaths[i]);\n\n await fs.mkdir(path.dirname(newThumbPath), { recursive: true });\n\n try {\n await fs.rename(oldThumbPath, newThumbPath);\n } catch {\n // Thumbnail might not exist\n }\n }\n\n // If file was in our R2, rename in cloud too (server-side)\n if (isInOurR2) {\n await moveInCdn(oldKey, newKey, hasThumbnails);\n\n // Clean up local files (they're now on CDN only)\n try {\n await fs.unlink(absoluteNewPath);\n } catch {\n /* ignore */\n }\n await deleteLocalThumbnails(newKey);\n }\n\n delete meta[oldKey];\n meta[newKey] = entry;\n await saveMeta(meta);\n }\n\n const newPath = path.join(path.dirname(safePath), sanitizedName);\n return jsonResponse({ success: true, newPath });\n } catch (error) {\n console.error(\"Failed to rename:\", error);\n return jsonResponse({ error: \"Failed to rename\" }, { status: 500 });\n }\n}\n\nexport async function handleRenameStream(request: Request) {\n const encoder = new TextEncoder();\n const publicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/$/, \"\");\n\n const stream = new ReadableStream({\n async start(controller) {\n const sendEvent = (data: object) => {\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\\n\\n`));\n };\n\n try {\n const { oldPath, newName, operationId } = await request.json();\n\n if (!oldPath || !newName) {\n sendEvent({\n type: \"error\",\n message: \"Path and new name are required\",\n });\n controller.close();\n return;\n }\n\n // Helper to check if operation was cancelled\n const isCancelled = () =>\n operationId ? isOperationCancelled(operationId) : false;\n\n const safePath = oldPath.replace(/\\.\\./g, \"\");\n const absoluteOldPath = getWorkspacePath(safePath);\n\n if (!absoluteOldPath.startsWith(getPublicPath())) {\n sendEvent({ type: \"error\", message: \"Invalid path\" });\n controller.close();\n return;\n }\n\n const oldRelativePath = safePath.replace(/^public\\//, \"\");\n const isImagePath = isImageFile(path.basename(oldPath));\n\n // Check if item exists and if it's a file or folder\n let hasLocalItem = false;\n let isFile = true;\n let isVirtualFolder = false;\n try {\n const stats = await fs.stat(absoluteOldPath);\n hasLocalItem = true;\n isFile = stats.isFile();\n } catch {\n // Check if it's a cloud-only file or virtual folder\n const meta = await loadMeta();\n const oldKey = \"/\" + oldRelativePath;\n const entry = meta[oldKey] as MetaEntry | undefined;\n\n if (entry) {\n // Cloud-only file\n isFile = true;\n } else {\n // Check if it's a virtual folder (folder that only exists in meta keys)\n const folderPrefix = oldKey + \"/\";\n const hasChildrenInMeta = Object.keys(meta).some((key) =>\n key.startsWith(folderPrefix)\n );\n\n if (hasChildrenInMeta) {\n // Virtual folder - exists only in meta\n isFile = false;\n isVirtualFolder = true;\n } else {\n sendEvent({ type: \"error\", message: \"File or folder not found\" });\n controller.close();\n return;\n }\n }\n }\n\n // Slugify name based on type\n const sanitizedName = isFile\n ? slugifyFilename(newName)\n : slugifyFolderName(newName);\n if (!sanitizedName) {\n sendEvent({ type: \"error\", message: \"Invalid name\" });\n controller.close();\n return;\n }\n\n const parentDir = path.dirname(absoluteOldPath);\n const absoluteNewPath = path.join(parentDir, sanitizedName);\n const newRelativePath = path.join(\n path.dirname(oldRelativePath),\n sanitizedName\n );\n const newPath = path.join(path.dirname(safePath), sanitizedName);\n\n // Check if destination exists\n const meta = await loadMeta();\n const cdnUrls = getCdnUrls(meta);\n\n // For files, check new key doesn't exist\n if (isFile) {\n const newKey = \"/\" + newRelativePath;\n if (meta[newKey]) {\n sendEvent({\n type: \"error\",\n message: \"An item with this name already exists\",\n });\n controller.close();\n return;\n }\n }\n\n // Only check local path if not a virtual folder\n if (!isVirtualFolder) {\n try {\n await fs.access(absoluteNewPath);\n sendEvent({\n type: \"error\",\n message: \"An item with this name already exists\",\n });\n controller.close();\n return;\n } catch {\n // Good - doesn't exist\n }\n }\n\n // For virtual folders, check if new prefix would conflict with existing meta keys\n if (isVirtualFolder) {\n const newPrefix = \"/\" + newRelativePath + \"/\";\n const hasConflict = Object.keys(meta).some((key) =>\n key.startsWith(newPrefix)\n );\n if (hasConflict) {\n sendEvent({\n type: \"error\",\n message: \"A folder with this name already exists\",\n });\n controller.close();\n return;\n }\n }\n\n // ========== FOLDER RENAME ==========\n if (!isFile) {\n // Collect all items in the folder that need meta updates\n const oldPrefix = \"/\" + oldRelativePath + \"/\";\n const newPrefix = \"/\" + newRelativePath + \"/\";\n\n // Find all meta entries under this folder\n const itemsToUpdate: Array<{\n oldKey: string;\n newKey: string;\n entry: MetaEntry;\n }> = [];\n for (const [key, entry] of Object.entries(meta)) {\n if (\n key.startsWith(oldPrefix) &&\n entry &&\n typeof entry === \"object\"\n ) {\n const newKey = key.replace(oldPrefix, newPrefix);\n itemsToUpdate.push({\n oldKey: key,\n newKey,\n entry: entry as MetaEntry,\n });\n }\n }\n\n const total = itemsToUpdate.length + 1; // +1 for the folder rename itself\n sendEvent({\n type: \"start\",\n total,\n message: `Renaming folder with ${itemsToUpdate.length} item(s)...`,\n });\n\n // Step 1: Rename the local folder and thumbnail folders\n if (hasLocalItem) {\n await fs.rename(absoluteOldPath, absoluteNewPath);\n\n // Also rename thumbnail directories\n // Thumbnails are at /images/folder/... so we need to rename those too\n const imagesDir = getPublicPath(\"/images\");\n const oldThumbFolder = path.join(imagesDir, oldRelativePath);\n const newThumbFolder = path.join(imagesDir, newRelativePath);\n try {\n await fs.access(oldThumbFolder);\n await fs.mkdir(path.dirname(newThumbFolder), { recursive: true });\n await fs.rename(oldThumbFolder, newThumbFolder);\n } catch {\n // Thumbnail folder might not exist\n }\n }\n sendEvent({\n type: \"progress\",\n current: 1,\n total,\n renamed: 1,\n message: \"Renamed folder\",\n });\n\n // Step 2: Update each item in the folder\n let renamed = 1;\n\n // Helper for cleanup on cancel\n const handleRenameCancel = async () => {\n await saveMeta(meta);\n // Clean up empty folders\n await deleteEmptyFolders(absoluteOldPath);\n const oldThumbFolder = path.join(\n getPublicPath(\"/images\"),\n oldRelativePath\n );\n await deleteEmptyFolders(oldThumbFolder);\n sendEvent({ type: \"complete\", renamed, newPath, cancelled: true });\n controller.close();\n };\n\n for (const item of itemsToUpdate) {\n // Check for cancellation\n if (isCancelled()) {\n await handleRenameCancel();\n return;\n }\n const { oldKey, newKey, entry } = item;\n const isInCloud = entry.c !== undefined;\n const fileCdnUrl = isInCloud ? cdnUrls[entry.c!] : undefined;\n const isInOurR2 = isInCloud && fileCdnUrl === publicUrl;\n const hasThumbnails = isProcessed(entry);\n\n // If in our R2, rename using server-side copy\n if (isInOurR2) {\n try {\n await moveInCdn(oldKey, newKey, hasThumbnails);\n\n // Clean up any local files that might exist after folder rename\n const localFilePath = getPublicPath(newKey);\n try {\n await fs.unlink(localFilePath);\n } catch {\n /* ignore */\n }\n if (hasThumbnails) {\n await deleteLocalThumbnails(newKey);\n }\n } catch (err) {\n console.error(`Failed to rename in CDN ${oldKey}:`, err);\n }\n }\n\n // Update meta entry\n delete meta[oldKey];\n meta[newKey] = entry;\n await saveMeta(meta);\n\n renamed++;\n sendEvent({\n type: \"progress\",\n current: renamed,\n total,\n renamed,\n message: `Renamed ${path.basename(newKey)}`,\n });\n }\n\n // Clean up empty folders (old folder location and old thumbnail location)\n await deleteEmptyFolders(absoluteOldPath);\n const oldThumbFolder = path.join(\n getPublicPath(\"/images\"),\n oldRelativePath\n );\n await deleteEmptyFolders(oldThumbFolder);\n\n sendEvent({ type: \"complete\", renamed, newPath });\n controller.close();\n return;\n }\n\n // ========== SINGLE FILE RENAME ==========\n const oldKey = \"/\" + oldRelativePath;\n const newKey = \"/\" + newRelativePath;\n const entry = meta[oldKey] as MetaEntry | undefined;\n const isInCloud = entry?.c !== undefined;\n const fileCdnUrl =\n isInCloud && entry?.c !== undefined ? cdnUrls[entry.c] : undefined;\n const isInOurR2 = isInCloud && fileCdnUrl === publicUrl;\n const hasThumbnails = entry ? isProcessed(entry) : false;\n\n sendEvent({ type: \"start\", total: 1, message: \"Renaming file...\" });\n\n // Handle cloud-only file (server-side rename in R2)\n if (isInOurR2 && !hasLocalItem) {\n await moveInCdn(oldKey, newKey, hasThumbnails);\n\n delete meta[oldKey];\n if (entry) meta[newKey] = entry;\n await saveMeta(meta);\n\n sendEvent({ type: \"complete\", renamed: 1, newPath });\n controller.close();\n return;\n }\n\n // Handle local file rename\n if (hasLocalItem) {\n await fs.rename(absoluteOldPath, absoluteNewPath);\n }\n\n if (isImagePath && entry) {\n const oldThumbPaths = getAllThumbnailPaths(oldKey);\n const newThumbPaths = getAllThumbnailPaths(newKey);\n\n for (let i = 0; i < oldThumbPaths.length; i++) {\n const oldThumbPath = getPublicPath(oldThumbPaths[i]);\n const newThumbPath = getPublicPath(newThumbPaths[i]);\n\n await fs.mkdir(path.dirname(newThumbPath), { recursive: true });\n\n try {\n await fs.rename(oldThumbPath, newThumbPath);\n } catch {\n /* skip */\n }\n }\n\n if (isInOurR2) {\n // Server-side rename in R2\n await moveInCdn(oldKey, newKey, hasThumbnails);\n\n // Clean up local files (they're now on CDN only)\n try {\n await fs.unlink(absoluteNewPath);\n } catch {\n /* ignore */\n }\n await deleteLocalThumbnails(newKey);\n }\n\n delete meta[oldKey];\n meta[newKey] = entry;\n await saveMeta(meta);\n }\n\n sendEvent({ type: \"complete\", renamed: 1, newPath });\n controller.close();\n } catch (error) {\n console.error(\"Rename stream error:\", error);\n sendEvent({ type: \"error\", message: \"Failed to rename\" });\n controller.close();\n }\n },\n });\n\n return streamResponse(stream);\n}\n\nexport async function handleMoveStream(request: Request) {\n const encoder = new TextEncoder();\n\n const stream = new ReadableStream({\n async start(controller) {\n const sendEvent = (data: object) => {\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\\n\\n`));\n };\n\n try {\n const { paths, destination, operationId } = await request.json();\n\n if (!paths || !Array.isArray(paths) || paths.length === 0) {\n sendEvent({ type: \"error\", message: \"Paths are required\" });\n controller.close();\n return;\n }\n\n if (!destination || typeof destination !== \"string\") {\n sendEvent({ type: \"error\", message: \"Destination is required\" });\n controller.close();\n return;\n }\n\n // Helper to check if operation was cancelled\n const isCancelled = () =>\n operationId ? isOperationCancelled(operationId) : false;\n\n const safeDestination = destination.replace(/\\.\\./g, \"\");\n const absoluteDestination = getWorkspacePath(safeDestination);\n\n if (!absoluteDestination.startsWith(getPublicPath())) {\n sendEvent({ type: \"error\", message: \"Invalid destination\" });\n controller.close();\n return;\n }\n\n // Ensure destination folder exists\n await fs.mkdir(absoluteDestination, { recursive: true });\n\n const meta = await loadMeta();\n const cdnUrls = getCdnUrls(meta);\n const r2PublicUrl =\n process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/$/, \"\") || \"\";\n\n const moved: string[] = [];\n const errors: string[] = [];\n const sourceFolders = new Set<string>();\n\n // Pre-calculate total files to move (expand folders and virtual folders)\n let totalFiles = 0;\n const expandedItems: Array<{\n itemPath: string;\n safePath: string;\n itemName: string;\n oldKey: string;\n newKey: string;\n newAbsolutePath: string;\n isVirtualFolder: boolean;\n virtualFolderItems?: Array<{\n oldKey: string;\n newKey: string;\n entry: MetaEntry;\n }>;\n }> = [];\n\n for (const itemPath of paths) {\n const safePath = itemPath.replace(/\\.\\./g, \"\");\n const itemName = path.basename(safePath);\n const oldRelativePath = safePath.replace(/^public\\/?/, \"\");\n const destWithoutPublic = safeDestination.replace(/^public\\/?/, \"\");\n const newRelativePath = destWithoutPublic\n ? path.join(destWithoutPublic, itemName)\n : itemName;\n const oldKey = \"/\" + oldRelativePath;\n const newKey = \"/\" + newRelativePath;\n const newAbsolutePath = path.join(absoluteDestination, itemName);\n const absolutePath = getWorkspacePath(safePath);\n\n // Check if it's a physical item\n let hasLocalItem = false;\n let isDirectory = false;\n try {\n const stats = await fs.stat(absolutePath);\n hasLocalItem = true;\n isDirectory = stats.isDirectory();\n } catch {\n // Check if it's a virtual folder\n }\n\n if (hasLocalItem && isDirectory) {\n // Count files in physical directory\n const countFilesRecursive = async (\n dir: string\n ): Promise<number> => {\n let count = 0;\n const entries = await fs.readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory()) {\n count += await countFilesRecursive(\n path.join(dir, entry.name)\n );\n } else {\n count++;\n }\n }\n return count;\n };\n const localFileCount = await countFilesRecursive(absolutePath);\n\n // Also count cloud-only files in meta that aren't local\n const folderPrefix = oldKey + \"/\";\n let cloudOnlyCount = 0;\n for (const metaKey of Object.keys(meta)) {\n if (metaKey.startsWith(folderPrefix)) {\n const relPath = metaKey.slice(folderPrefix.length);\n const localPath = path.join(absolutePath, relPath);\n try {\n await fs.access(localPath);\n // File exists locally, already counted\n } catch {\n // Cloud-only file\n cloudOnlyCount++;\n }\n }\n }\n\n totalFiles += localFileCount + cloudOnlyCount;\n expandedItems.push({\n itemPath,\n safePath,\n itemName,\n oldKey,\n newKey,\n newAbsolutePath,\n isVirtualFolder: false,\n });\n } else if (!hasLocalItem) {\n // Check for virtual folder\n const folderPrefix = oldKey + \"/\";\n const virtualItems: Array<{\n oldKey: string;\n newKey: string;\n entry: MetaEntry;\n }> = [];\n for (const [key, metaEntry] of Object.entries(meta)) {\n if (\n key.startsWith(folderPrefix) &&\n metaEntry &&\n typeof metaEntry === \"object\"\n ) {\n const relativePath = key.slice(folderPrefix.length);\n const destNewKey = newKey + \"/\" + relativePath;\n virtualItems.push({\n oldKey: key,\n newKey: destNewKey,\n entry: metaEntry as MetaEntry,\n });\n }\n }\n if (virtualItems.length > 0) {\n totalFiles += virtualItems.length;\n expandedItems.push({\n itemPath,\n safePath,\n itemName,\n oldKey,\n newKey,\n newAbsolutePath,\n isVirtualFolder: true,\n virtualFolderItems: virtualItems,\n });\n // Track source folder for cleanup\n sourceFolders.add(absolutePath);\n } else {\n // Single file (local or cloud)\n totalFiles++;\n expandedItems.push({\n itemPath,\n safePath,\n itemName,\n oldKey,\n newKey,\n newAbsolutePath,\n isVirtualFolder: false,\n });\n }\n } else {\n // Single local file\n totalFiles++;\n expandedItems.push({\n itemPath,\n safePath,\n itemName,\n oldKey,\n newKey,\n newAbsolutePath,\n isVirtualFolder: false,\n });\n }\n }\n\n sendEvent({ type: \"start\", total: totalFiles });\n let processedFiles = 0;\n\n // Track individual files moved (for accurate count on cancel)\n let filesMoved = 0;\n\n // Helper to do cleanup and send cancel complete\n const handleCancel = async () => {\n await saveMeta(meta);\n // Clean up empty source folders\n for (const folder of sourceFolders) {\n await deleteEmptyFolders(folder);\n }\n // Clean up destination if it became empty\n await deleteEmptyFolders(absoluteDestination);\n sendEvent({\n type: \"complete\",\n moved: filesMoved,\n errors: errors.length,\n errorMessages: errors,\n cancelled: true,\n });\n controller.close();\n };\n\n for (const expandedItem of expandedItems) {\n // Check for cancellation before processing each item\n if (isCancelled()) {\n await handleCancel();\n return;\n }\n\n const {\n itemPath,\n safePath,\n itemName,\n oldKey,\n newKey,\n newAbsolutePath,\n isVirtualFolder,\n virtualFolderItems,\n } = expandedItem;\n\n // Handle virtual folder\n if (isVirtualFolder && virtualFolderItems) {\n for (const vItem of virtualFolderItems) {\n // Check for cancellation before processing each virtual item\n if (isCancelled()) {\n await handleCancel();\n return;\n }\n const itemEntry = vItem.entry;\n const isItemInCloud = itemEntry.c !== undefined;\n const itemCdnUrl = isItemInCloud\n ? cdnUrls[itemEntry.c!]\n : undefined;\n const isItemInR2 = isItemInCloud && itemCdnUrl === r2PublicUrl;\n const itemHasThumbnails = isProcessed(itemEntry);\n\n let vItemMoved = false;\n\n if (isItemInR2) {\n try {\n // Server-side copy+delete in R2 (no download/upload needed)\n await moveInCdn(\n vItem.oldKey,\n vItem.newKey,\n itemHasThumbnails\n );\n vItemMoved = true;\n filesMoved++;\n } catch (err) {\n console.error(\n `Failed to move cloud item ${vItem.oldKey}:`,\n err\n );\n // File doesn't exist on CDN - remove orphaned meta entry\n delete meta[vItem.oldKey];\n await saveMeta(meta);\n }\n }\n\n // Only update meta if file was successfully moved\n if (vItemMoved) {\n delete meta[vItem.oldKey];\n meta[vItem.newKey] = itemEntry;\n await saveMeta(meta);\n // Track source folder for cleanup\n const oldAbsPath = getPublicPath(vItem.oldKey);\n sourceFolders.add(path.dirname(oldAbsPath));\n }\n\n processedFiles++;\n sendEvent({\n type: \"progress\",\n current: processedFiles,\n total: totalFiles,\n moved: filesMoved,\n percent: Math.round((processedFiles / totalFiles) * 100),\n currentFile: path.basename(vItem.newKey),\n });\n }\n\n // Clean up temp folders created for virtual folder cloud file processing\n // Clean up the new folder location (where temp files were downloaded)\n const newFolderPath = getPublicPath(newKey);\n await deleteEmptyFolders(newFolderPath);\n // Clean up thumbnail folder\n const newThumbFolder = path.join(\n getPublicPath(\"images\"),\n newKey.slice(1)\n );\n await deleteEmptyFolders(newThumbFolder);\n // Track old folder for cleanup\n const oldFolderPath = getPublicPath(oldKey);\n sourceFolders.add(oldFolderPath);\n\n moved.push(itemPath);\n continue;\n }\n\n // Check if destination already exists in meta\n if (meta[newKey]) {\n errors.push(`${itemName} already exists in destination`);\n processedFiles++;\n sendEvent({\n type: \"progress\",\n current: processedFiles,\n total: totalFiles,\n moved: moved.length,\n percent: Math.round((processedFiles / totalFiles) * 100),\n currentFile: itemName,\n });\n continue;\n }\n\n const entry = meta[oldKey] as MetaEntry | undefined;\n const isImage = isImageFile(itemName);\n\n // Determine if cloud or remote\n const isInCloud = entry?.c !== undefined;\n const fileCdnUrl =\n isInCloud && entry.c !== undefined ? cdnUrls[entry.c] : undefined;\n const isRemote =\n isInCloud && (!r2PublicUrl || fileCdnUrl !== r2PublicUrl);\n const isPushedToR2 =\n isInCloud && r2PublicUrl && fileCdnUrl === r2PublicUrl;\n const hasProcessedThumbnails = isProcessed(entry);\n\n try {\n // Track source folder for cleanup\n const sourceFolder = path.dirname(getWorkspacePath(safePath));\n sourceFolders.add(sourceFolder);\n\n if (isRemote) {\n // ===== REMOTE FILE (external CDN) =====\n // Download to local with new path\n const remoteUrl = `${fileCdnUrl}${oldKey}`;\n const buffer = await downloadFromRemoteUrl(remoteUrl);\n\n await fs.mkdir(path.dirname(newAbsolutePath), {\n recursive: true,\n });\n await fs.writeFile(newAbsolutePath, buffer);\n\n // Create new entry without CDN reference (now local)\n const newEntry: MetaEntry = {\n o: entry?.o,\n b: entry?.b,\n };\n delete meta[oldKey];\n meta[newKey] = newEntry;\n await saveMeta(meta);\n moved.push(itemPath);\n filesMoved++;\n processedFiles++;\n sendEvent({\n type: \"progress\",\n current: processedFiles,\n total: totalFiles,\n moved: filesMoved,\n percent: Math.round((processedFiles / totalFiles) * 100),\n currentFile: itemName,\n });\n } else if (isPushedToR2) {\n // ===== CLOUD FILE (R2) - server-side move =====\n // Works for both images and non-images\n await moveInCdn(oldKey, newKey, hasProcessedThumbnails);\n\n // Keep same entry, just update the key\n delete meta[oldKey];\n if (entry) {\n meta[newKey] = entry;\n }\n await saveMeta(meta);\n moved.push(itemPath);\n filesMoved++;\n processedFiles++;\n sendEvent({\n type: \"progress\",\n current: processedFiles,\n total: totalFiles,\n moved: filesMoved,\n percent: Math.round((processedFiles / totalFiles) * 100),\n currentFile: itemName,\n });\n } else {\n // ===== LOCAL FILE =====\n const absolutePath = getWorkspacePath(safePath);\n\n if (absoluteDestination.startsWith(absolutePath + path.sep)) {\n errors.push(`Cannot move ${itemName} into itself`);\n processedFiles++;\n sendEvent({\n type: \"progress\",\n current: processedFiles,\n total: totalFiles,\n moved: filesMoved,\n percent: Math.round((processedFiles / totalFiles) * 100),\n currentFile: itemName,\n });\n continue;\n }\n\n // Check if local file/folder exists\n try {\n await fs.access(absolutePath);\n } catch {\n errors.push(`${itemName} not found`);\n processedFiles++;\n sendEvent({\n type: \"progress\",\n current: processedFiles,\n total: totalFiles,\n moved: filesMoved,\n percent: Math.round((processedFiles / totalFiles) * 100),\n currentFile: itemName,\n });\n continue;\n }\n\n try {\n await fs.access(newAbsolutePath);\n errors.push(`${itemName} already exists in destination`);\n processedFiles++;\n sendEvent({\n type: \"progress\",\n current: processedFiles,\n total: totalFiles,\n moved: filesMoved,\n percent: Math.round((processedFiles / totalFiles) * 100),\n currentFile: itemName,\n });\n continue;\n } catch {\n // Good\n }\n\n const stats = await fs.stat(absolutePath);\n\n if (stats.isFile()) {\n // ===== SINGLE LOCAL FILE =====\n await fs.mkdir(path.dirname(newAbsolutePath), {\n recursive: true,\n });\n await fs.rename(absolutePath, newAbsolutePath);\n\n if (isImage && entry) {\n const oldThumbPaths = getAllThumbnailPaths(oldKey);\n const newThumbPaths = getAllThumbnailPaths(newKey);\n\n for (let j = 0; j < oldThumbPaths.length; j++) {\n const oldThumbPath = getPublicPath(oldThumbPaths[j]);\n const newThumbPath = getPublicPath(newThumbPaths[j]);\n\n try {\n await fs.access(oldThumbPath);\n sourceFolders.add(path.dirname(oldThumbPath));\n await fs.mkdir(path.dirname(newThumbPath), {\n recursive: true,\n });\n await fs.rename(oldThumbPath, newThumbPath);\n } catch {\n // Thumbnail doesn't exist\n }\n }\n\n // Check if file was synced to cloud - needs re-upload with new key\n const fileIsInCloud = entry.c !== undefined;\n const fileCdnUrl = fileIsInCloud\n ? cdnUrls[entry.c!]\n : undefined;\n const fileIsInR2 =\n fileIsInCloud && fileCdnUrl === r2PublicUrl;\n const fileHasThumbs = isProcessed(entry);\n\n if (fileIsInR2) {\n // Re-upload with new key\n await deleteFromCdn(oldKey, fileHasThumbs);\n await uploadOriginalToCdn(newKey);\n if (fileHasThumbs) {\n await uploadToCdn(newKey);\n }\n }\n\n delete meta[oldKey];\n meta[newKey] = entry;\n await saveMeta(meta);\n }\n\n moved.push(itemPath);\n filesMoved++;\n processedFiles++;\n sendEvent({\n type: \"progress\",\n current: processedFiles,\n total: totalFiles,\n moved: filesMoved,\n percent: Math.round((processedFiles / totalFiles) * 100),\n currentFile: itemName,\n });\n } else if (stats.isDirectory()) {\n // ===== LOCAL DIRECTORY - iterate through files =====\n const oldPrefix = oldKey + \"/\";\n const newPrefix = newKey + \"/\";\n\n // Collect all files in directory (local + cloud-only from meta)\n const localFiles: Array<{\n relativePath: string;\n isImage: boolean;\n }> = [];\n\n const collectLocalFiles = async (\n dir: string,\n relativeDir: string\n ) => {\n const entries = await fs.readdir(dir, {\n withFileTypes: true,\n });\n for (const dirEntry of entries) {\n const entryRelPath = relativeDir\n ? `${relativeDir}/${dirEntry.name}`\n : dirEntry.name;\n if (dirEntry.isDirectory()) {\n await collectLocalFiles(\n path.join(dir, dirEntry.name),\n entryRelPath\n );\n } else {\n localFiles.push({\n relativePath: entryRelPath,\n isImage: isImageFile(dirEntry.name),\n });\n }\n }\n };\n await collectLocalFiles(absolutePath, \"\");\n\n // Also find cloud-only files from meta that aren't local\n const cloudOnlyFiles: Array<{\n oldKey: string;\n newKey: string;\n entry: MetaEntry;\n }> = [];\n for (const [metaKey, metaEntry] of Object.entries(meta)) {\n if (\n metaKey.startsWith(oldPrefix) &&\n metaEntry &&\n typeof metaEntry === \"object\"\n ) {\n const relPath = metaKey.slice(oldPrefix.length);\n const localPath = path.join(absolutePath, relPath);\n try {\n await fs.access(localPath);\n // File exists locally, will be handled by localFiles\n } catch {\n // Cloud-only file\n cloudOnlyFiles.push({\n oldKey: metaKey,\n newKey: newPrefix + relPath,\n entry: metaEntry as MetaEntry,\n });\n }\n }\n }\n\n // Process each local file\n for (const localFile of localFiles) {\n // Check for cancellation\n if (isCancelled()) {\n await handleCancel();\n return;\n }\n const fileOldPath = path.join(\n absolutePath,\n localFile.relativePath\n );\n const fileNewPath = path.join(\n newAbsolutePath,\n localFile.relativePath\n );\n const fileOldKey = oldPrefix + localFile.relativePath;\n const fileNewKey = newPrefix + localFile.relativePath;\n const fileEntry = meta[fileOldKey] as MetaEntry | undefined;\n\n // Track source folder\n sourceFolders.add(path.dirname(fileOldPath));\n\n // Move the file\n await fs.mkdir(path.dirname(fileNewPath), {\n recursive: true,\n });\n await fs.rename(fileOldPath, fileNewPath);\n filesMoved++;\n\n if (localFile.isImage && fileEntry) {\n // Move thumbnails\n const oldThumbPaths = getAllThumbnailPaths(fileOldKey);\n const newThumbPaths = getAllThumbnailPaths(fileNewKey);\n\n for (let t = 0; t < oldThumbPaths.length; t++) {\n const oldThumbPath = getPublicPath(oldThumbPaths[t]);\n const newThumbPath = getPublicPath(newThumbPaths[t]);\n try {\n await fs.access(oldThumbPath);\n sourceFolders.add(path.dirname(oldThumbPath));\n await fs.mkdir(path.dirname(newThumbPath), {\n recursive: true,\n });\n await fs.rename(oldThumbPath, newThumbPath);\n } catch {\n /* skip */\n }\n }\n\n // Check if synced to cloud - move in CDN with new key\n const fileIsInCloud = fileEntry.c !== undefined;\n const fileCdnUrl = fileIsInCloud\n ? cdnUrls[fileEntry.c!]\n : undefined;\n const fileIsInR2 =\n fileIsInCloud && fileCdnUrl === r2PublicUrl;\n const fileHasThumbs = isProcessed(fileEntry);\n\n if (fileIsInR2) {\n // Server-side copy+delete in R2 (faster than re-upload)\n await moveInCdn(fileOldKey, fileNewKey, fileHasThumbs);\n }\n\n delete meta[fileOldKey];\n meta[fileNewKey] = fileEntry;\n await saveMeta(meta);\n }\n\n processedFiles++;\n sendEvent({\n type: \"progress\",\n current: processedFiles,\n total: totalFiles,\n moved: filesMoved,\n percent: Math.round((processedFiles / totalFiles) * 100),\n currentFile: path.basename(localFile.relativePath),\n });\n }\n\n // Process cloud-only files within the directory\n for (const cloudFile of cloudOnlyFiles) {\n // Check for cancellation\n if (isCancelled()) {\n await handleCancel();\n return;\n }\n const cloudEntry = cloudFile.entry;\n const cloudIsInCloud = cloudEntry.c !== undefined;\n const cloudCdnUrl = cloudIsInCloud\n ? cdnUrls[cloudEntry.c!]\n : undefined;\n const cloudIsInR2 =\n cloudIsInCloud && cloudCdnUrl === r2PublicUrl;\n const cloudHasThumbs = isProcessed(cloudEntry);\n\n let cloudFileMoved = false;\n\n if (cloudIsInR2) {\n try {\n // Server-side copy+delete in R2 (no download/upload needed)\n await moveInCdn(\n cloudFile.oldKey,\n cloudFile.newKey,\n cloudHasThumbs\n );\n cloudFileMoved = true;\n filesMoved++;\n } catch (err) {\n console.error(\n `Failed to move cloud file ${cloudFile.oldKey}:`,\n err\n );\n // File doesn't exist on CDN - remove from meta since it's orphaned\n delete meta[cloudFile.oldKey];\n await saveMeta(meta);\n }\n }\n\n // Only update meta if file was successfully moved\n if (cloudFileMoved) {\n delete meta[cloudFile.oldKey];\n meta[cloudFile.newKey] = cloudEntry;\n await saveMeta(meta);\n }\n\n processedFiles++;\n sendEvent({\n type: \"progress\",\n current: processedFiles,\n total: totalFiles,\n moved: filesMoved,\n percent: Math.round((processedFiles / totalFiles) * 100),\n currentFile: path.basename(cloudFile.newKey),\n });\n }\n\n // Clean up old empty source folder\n sourceFolders.add(absolutePath);\n\n // Clean up old thumbnail folder\n const oldThumbRelPath = oldKey.slice(1);\n const oldThumbFolder = path.join(\n getPublicPath(\"images\"),\n oldThumbRelPath\n );\n sourceFolders.add(oldThumbFolder);\n\n moved.push(itemPath);\n }\n }\n } catch (err) {\n console.error(`Failed to move ${itemName}:`, err);\n errors.push(`Failed to move ${itemName}`);\n processedFiles++;\n sendEvent({\n type: \"progress\",\n current: processedFiles,\n total: totalFiles,\n moved: filesMoved,\n percent: Math.round((processedFiles / totalFiles) * 100),\n currentFile: itemName,\n });\n }\n }\n\n await saveMeta(meta);\n\n // Clean up empty source folders\n for (const folder of sourceFolders) {\n await deleteEmptyFolders(folder);\n }\n\n // Clean up destination folder if it was created but is now empty\n // (happens when moving virtual folders with server-side copy)\n await deleteEmptyFolders(absoluteDestination);\n\n sendEvent({\n type: \"complete\",\n moved: filesMoved,\n errors: errors.length,\n errorMessages: errors,\n });\n } catch (error) {\n console.error(\"Failed to move:\", error);\n sendEvent({ type: \"error\", message: \"Failed to move items\" });\n } finally {\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n}\n\nexport async function handleMove(request: Request) {\n try {\n const { paths, destination } = await request.json();\n\n if (!paths || !Array.isArray(paths) || paths.length === 0) {\n return jsonResponse({ error: \"Paths are required\" }, { status: 400 });\n }\n\n if (!destination || typeof destination !== \"string\") {\n return jsonResponse(\n { error: \"Destination is required\" },\n { status: 400 }\n );\n }\n\n const safeDestination = destination.replace(/\\.\\./g, \"\");\n const absoluteDestination = getWorkspacePath(safeDestination);\n\n if (!absoluteDestination.startsWith(getPublicPath())) {\n return jsonResponse({ error: \"Invalid destination\" }, { status: 400 });\n }\n\n // Ensure destination folder exists (create if needed)\n await fs.mkdir(absoluteDestination, { recursive: true });\n\n const moved: string[] = [];\n const errors: string[] = [];\n const sourceFolders = new Set<string>();\n const meta = await loadMeta();\n const cdnUrls = getCdnUrls(meta);\n const r2PublicUrl =\n process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/$/, \"\") || \"\";\n let metaChanged = false;\n\n for (const itemPath of paths) {\n const safePath = itemPath.replace(/\\.\\./g, \"\");\n const itemName = path.basename(safePath);\n const newAbsolutePath = path.join(absoluteDestination, itemName);\n\n // Build meta keys\n const oldRelativePath = safePath.replace(/^public\\/?/, \"\");\n const destWithoutPublic = safeDestination.replace(/^public\\/?/, \"\");\n const newRelativePath = destWithoutPublic\n ? path.join(destWithoutPublic, itemName)\n : itemName;\n const oldKey = \"/\" + oldRelativePath;\n const newKey = \"/\" + newRelativePath;\n\n // Track source folder for cleanup\n sourceFolders.add(path.dirname(getWorkspacePath(safePath)));\n\n // Check if destination already exists in meta\n if (meta[newKey]) {\n errors.push(`${itemName} already exists in destination`);\n continue;\n }\n\n const entry = meta[oldKey] as MetaEntry | undefined;\n const isImage = isImageFile(itemName);\n\n // Determine if cloud or remote\n const isInCloud = entry?.c !== undefined;\n const fileCdnUrl =\n isInCloud && entry.c !== undefined ? cdnUrls[entry.c] : undefined;\n const isRemote =\n isInCloud && (!r2PublicUrl || fileCdnUrl !== r2PublicUrl);\n const isPushedToR2 =\n isInCloud && r2PublicUrl && fileCdnUrl === r2PublicUrl;\n const hasProcessedThumbnails = isProcessed(entry);\n\n try {\n if (isRemote) {\n // ===== REMOTE FILE: Download from external URL, save locally, remove c =====\n const remoteUrl = `${fileCdnUrl}${oldKey}`;\n const buffer = await downloadFromRemoteUrl(remoteUrl);\n\n // Save to new local location\n await fs.mkdir(path.dirname(newAbsolutePath), { recursive: true });\n await fs.writeFile(newAbsolutePath, buffer);\n\n // Update meta: remove c (now local), keep other properties\n const newEntry: MetaEntry = {\n o: entry?.o,\n b: entry?.b,\n // Don't copy thumbnail dims since remote files don't have local thumbnails\n // Don't copy c since it's now local\n };\n delete meta[oldKey];\n meta[newKey] = newEntry;\n metaChanged = true;\n moved.push(itemPath);\n } else if (isPushedToR2) {\n // ===== CLOUD FILE (R2): Server-side move =====\n await moveInCdn(oldKey, newKey, hasProcessedThumbnails);\n\n // Update meta with same entry, new key\n delete meta[oldKey];\n if (entry) {\n meta[newKey] = entry;\n }\n metaChanged = true;\n moved.push(itemPath);\n } else {\n // ===== LOCAL FILE: Use standard fs.rename =====\n const absolutePath = getWorkspacePath(safePath);\n\n if (absoluteDestination.startsWith(absolutePath + path.sep)) {\n errors.push(`Cannot move ${itemName} into itself`);\n continue;\n }\n\n try {\n await fs.access(absolutePath);\n } catch {\n errors.push(`${itemName} not found`);\n continue;\n }\n\n try {\n await fs.access(newAbsolutePath);\n errors.push(`${itemName} already exists in destination`);\n continue;\n } catch {\n // Good - doesn't exist\n }\n\n await fs.rename(absolutePath, newAbsolutePath);\n\n const stats = await fs.stat(newAbsolutePath);\n if (stats.isFile() && isImage && entry) {\n // Move local thumbnails\n const oldThumbPaths = getAllThumbnailPaths(oldKey);\n const newThumbPaths = getAllThumbnailPaths(newKey);\n\n for (let i = 0; i < oldThumbPaths.length; i++) {\n const oldThumbPath = getPublicPath(oldThumbPaths[i]);\n const newThumbPath = getPublicPath(newThumbPaths[i]);\n\n try {\n // Check if thumbnail exists before trying to move\n await fs.access(oldThumbPath);\n\n // Track thumbnail source folder for cleanup\n sourceFolders.add(path.dirname(oldThumbPath));\n\n // Create destination folder and move thumbnail\n await fs.mkdir(path.dirname(newThumbPath), { recursive: true });\n await fs.rename(oldThumbPath, newThumbPath);\n } catch {\n // Thumbnail doesn't exist, skip\n }\n }\n\n delete meta[oldKey];\n meta[newKey] = entry;\n metaChanged = true;\n } else if (stats.isDirectory()) {\n // Move folder: update all meta entries under this folder\n const oldPrefix = oldKey + \"/\";\n const newPrefix = newKey + \"/\";\n\n for (const key of Object.keys(meta)) {\n if (key.startsWith(oldPrefix)) {\n const newMetaKey = newPrefix + key.slice(oldPrefix.length);\n meta[newMetaKey] = meta[key];\n delete meta[key];\n metaChanged = true;\n }\n }\n\n // Also move the thumbnail folder\n const imagesDir = getPublicPath(\"images\");\n const oldThumbFolder = path.join(imagesDir, oldRelativePath);\n const newThumbFolder = path.join(imagesDir, newRelativePath);\n try {\n await fs.access(oldThumbFolder);\n // Track old thumbnail folder for cleanup\n sourceFolders.add(oldThumbFolder);\n await fs.mkdir(path.dirname(newThumbFolder), { recursive: true });\n await fs.rename(oldThumbFolder, newThumbFolder);\n } catch {\n // Thumbnail folder might not exist\n }\n }\n\n moved.push(itemPath);\n }\n } catch (err) {\n console.error(`Failed to move ${itemName}:`, err);\n errors.push(`Failed to move ${itemName}`);\n }\n }\n\n if (metaChanged) {\n await saveMeta(meta);\n }\n\n // Clean up empty source folders\n for (const folder of sourceFolders) {\n await deleteEmptyFolders(folder);\n }\n\n // Clean up destination folder if it was created but is now empty\n // (happens when moving virtual folders with server-side copy)\n await deleteEmptyFolders(absoluteDestination);\n\n return jsonResponse({\n success: errors.length === 0,\n moved,\n errors: errors.length > 0 ? errors : undefined,\n });\n } catch (error) {\n console.error(\"Failed to move:\", error);\n return jsonResponse({ error: \"Failed to move items\" }, { status: 500 });\n }\n}\n","import { promises as fs } from 'fs'\nimport path from 'path'\nimport { getPublicPath } from '../../config'\n\n/**\n * Check if a file is a hidden/system file that should be ignored\n * - Mac/Linux: files starting with .\n * - Windows: Thumbs.db, desktop.ini, etc.\n */\nexport function isHiddenOrSystemFile(filename: string): boolean {\n // Hidden files on Mac/Linux start with .\n if (filename.startsWith('.')) return true\n \n // Windows system files\n const windowsSystemFiles = ['thumbs.db', 'desktop.ini', 'ehthumbs.db', 'ehthumbs_vista.db']\n if (windowsSystemFiles.includes(filename.toLowerCase())) return true\n \n return false\n}\n\n/**\n * Recursively delete empty folders starting from the given path\n * Stops at the public folder boundary\n * Includes the images folder - it can be deleted if empty and will be recreated when needed\n * Ignores hidden and system files (deletes them if they're the only contents)\n */\nexport async function deleteEmptyFolders(folderPath: string): Promise<void> {\n const publicPath = getPublicPath()\n \n // Normalize paths for comparison\n const normalizedFolder = path.resolve(folderPath)\n const normalizedPublic = path.resolve(publicPath)\n \n // Don't delete the public folder itself\n if (normalizedFolder === normalizedPublic) {\n return\n }\n \n // Check if folder is inside public\n if (!normalizedFolder.startsWith(normalizedPublic)) {\n return\n }\n \n try {\n const entries = await fs.readdir(folderPath)\n \n // Filter out hidden/system files\n const meaningfulEntries = entries.filter(e => !isHiddenOrSystemFile(e))\n \n // If folder only contains hidden/system files (or is empty), delete it\n if (meaningfulEntries.length === 0) {\n // First delete any hidden/system files\n for (const entry of entries) {\n if (isHiddenOrSystemFile(entry)) {\n try {\n await fs.unlink(path.join(folderPath, entry))\n } catch {\n // Ignore deletion errors for system files\n }\n }\n }\n \n // Now delete the folder\n await fs.rmdir(folderPath)\n \n // Recursively check parent folder\n const parentFolder = path.dirname(folderPath)\n await deleteEmptyFolders(parentFolder)\n }\n } catch {\n // Folder doesn't exist or can't be read, ignore\n }\n}\n\n/**\n * Ensure a folder exists, creating it if necessary\n */\nexport async function ensureFolderExists(folderPath: string): Promise<void> {\n try {\n await fs.mkdir(folderPath, { recursive: true })\n } catch {\n // Already exists or can't be created\n }\n}\n\n/**\n * Recursively clean up all empty folders within a directory\n * Also deletes the directory itself if it becomes empty\n * Ignores hidden/system files (deletes them if they're the only contents)\n */\nexport async function cleanupEmptyFoldersRecursive(dir: string): Promise<boolean> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true })\n let isEmpty = true\n \n for (const entry of entries) {\n if (entry.isDirectory()) {\n const subDirEmpty = await cleanupEmptyFoldersRecursive(path.join(dir, entry.name))\n if (!subDirEmpty) isEmpty = false\n } else if (!isHiddenOrSystemFile(entry.name)) {\n // Non-hidden file exists\n isEmpty = false\n }\n }\n \n // Delete empty folder\n if (isEmpty) {\n // First delete any hidden/system files\n for (const entry of entries) {\n if (!entry.isDirectory() && isHiddenOrSystemFile(entry.name)) {\n try {\n await fs.unlink(path.join(dir, entry.name))\n } catch {\n // Ignore deletion errors\n }\n }\n }\n await fs.rmdir(dir)\n }\n \n return isEmpty\n } catch {\n return true\n }\n}\n","import { promises as fs } from \"fs\";\nimport path from \"path\";\nimport {\n S3Client,\n PutObjectCommand,\n DeleteObjectCommand,\n} from \"@aws-sdk/client-s3\";\nimport { jsonResponse } from \"./utils/response\";\nimport { getAllThumbnailPaths, isProcessed, type MetaEntry } from \"../types\";\nimport {\n loadMeta,\n saveMeta,\n isImageFile,\n getContentType,\n processImage,\n downloadFromCdn,\n uploadToCdn,\n uploadOriginalToCdn,\n deleteLocalThumbnails,\n deleteThumbnailsFromCdn,\n deleteOriginalFromCdn,\n getOrAddCdnIndex,\n getFileEntries,\n getMetaEntry,\n getCdnUrls,\n downloadFromRemoteUrl,\n} from \"./utils\";\nimport { getPublicPath } from \"../config\";\nimport {\n deleteEmptyFolders,\n cleanupEmptyFoldersRecursive,\n} from \"./utils/folders\";\n\n// Global cancellation tokens for streaming operations\nconst cancelledOperations = new Set<string>();\n\nexport function cancelOperation(operationId: string) {\n cancelledOperations.add(operationId);\n // Clean up after 60 seconds\n setTimeout(() => cancelledOperations.delete(operationId), 60000);\n}\n\nexport function isOperationCancelled(operationId: string): boolean {\n return cancelledOperations.has(operationId);\n}\n\nexport function clearCancelledOperation(operationId: string) {\n cancelledOperations.delete(operationId);\n}\n\nexport async function handleSync(request: Request) {\n const accountId = process.env.CLOUDFLARE_R2_ACCOUNT_ID;\n const accessKeyId = process.env.CLOUDFLARE_R2_ACCESS_KEY_ID;\n const secretAccessKey = process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY;\n const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;\n const publicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/\\s*$/, \"\");\n\n if (\n !accountId ||\n !accessKeyId ||\n !secretAccessKey ||\n !bucketName ||\n !publicUrl\n ) {\n return jsonResponse(\n {\n error: \"R2 not configured. Set CLOUDFLARE_R2_* environment variables.\",\n },\n { status: 400 }\n );\n }\n\n try {\n const { imageKeys } = (await request.json()) as { imageKeys: string[] };\n\n if (!imageKeys || !Array.isArray(imageKeys) || imageKeys.length === 0) {\n return jsonResponse({ error: \"No image keys provided\" }, { status: 400 });\n }\n\n const meta = await loadMeta();\n const cdnUrls = getCdnUrls(meta);\n\n // Get or add CDN URL to the _cdns array\n const cdnIndex = getOrAddCdnIndex(meta, publicUrl);\n\n const r2 = new S3Client({\n region: \"auto\",\n endpoint: `https://${accountId}.r2.cloudflarestorage.com`,\n credentials: { accessKeyId, secretAccessKey },\n });\n\n const pushed: string[] = [];\n const alreadyPushed: string[] = [];\n const errors: string[] = [];\n const sourceFolders = new Set<string>();\n\n for (let imageKey of imageKeys) {\n // Normalize key to have leading /\n if (!imageKey.startsWith(\"/\")) {\n imageKey = `/${imageKey}`;\n }\n\n const entry = getMetaEntry(meta, imageKey);\n if (!entry) {\n errors.push(`Image not found in meta: ${imageKey}. Run Scan first.`);\n continue;\n }\n\n // Check if already pushed to our R2\n const existingCdnUrl =\n entry.c !== undefined ? cdnUrls[entry.c] : undefined;\n const isAlreadyInOurR2 = existingCdnUrl === publicUrl;\n\n if (isAlreadyInOurR2) {\n alreadyPushed.push(imageKey);\n continue;\n }\n\n // Check if this is a remote image (in another CDN)\n const isRemote = entry.c !== undefined && existingCdnUrl !== publicUrl;\n\n try {\n let originalBuffer: Buffer;\n\n if (isRemote) {\n // Download from remote URL\n const remoteUrl = `${existingCdnUrl}${imageKey}`;\n originalBuffer = await downloadFromRemoteUrl(remoteUrl);\n } else {\n // Read from local file\n const originalLocalPath = getPublicPath(imageKey);\n try {\n originalBuffer = await fs.readFile(originalLocalPath);\n } catch {\n errors.push(`Original file not found: ${imageKey}`);\n continue;\n }\n }\n\n // Upload original to R2\n await r2.send(\n new PutObjectCommand({\n Bucket: bucketName,\n Key: imageKey.replace(/^\\//, \"\"),\n Body: originalBuffer,\n ContentType: getContentType(imageKey),\n })\n );\n\n // Upload thumbnails (only if processed locally, not for remote imports)\n if (!isRemote && isProcessed(entry)) {\n for (const thumbPath of getAllThumbnailPaths(imageKey)) {\n const localPath = getPublicPath(thumbPath);\n try {\n const fileBuffer = await fs.readFile(localPath);\n await r2.send(\n new PutObjectCommand({\n Bucket: bucketName,\n Key: thumbPath.replace(/^\\//, \"\"),\n Body: fileBuffer,\n ContentType: getContentType(thumbPath),\n })\n );\n } catch {\n // Thumbnail might not exist\n }\n }\n }\n\n entry.c = cdnIndex;\n\n // Delete local files (only for non-remote, local images being pushed)\n if (!isRemote) {\n const originalLocalPath = getPublicPath(imageKey);\n\n // Track source folder for cleanup\n sourceFolders.add(path.dirname(originalLocalPath));\n\n // Delete local thumbnails\n for (const thumbPath of getAllThumbnailPaths(imageKey)) {\n const localPath = getPublicPath(thumbPath);\n // Track thumbnail folder too\n sourceFolders.add(path.dirname(localPath));\n try {\n await fs.unlink(localPath);\n } catch {\n /* ignore */\n }\n }\n\n // Delete local original\n try {\n await fs.unlink(originalLocalPath);\n } catch {\n /* ignore */\n }\n }\n\n // Save meta after each successful push\n await saveMeta(meta);\n\n pushed.push(imageKey);\n } catch (error) {\n console.error(`Failed to push ${imageKey}:`, error);\n errors.push(`Failed to push: ${imageKey}`);\n }\n }\n\n // Clean up empty source folders\n for (const folder of sourceFolders) {\n await deleteEmptyFolders(folder);\n }\n\n return jsonResponse({\n success: true,\n pushed,\n alreadyPushed: alreadyPushed.length > 0 ? alreadyPushed : undefined,\n errors: errors.length > 0 ? errors : undefined,\n });\n } catch (error) {\n console.error(\"Failed to push:\", error);\n return jsonResponse({ error: \"Failed to push to CDN\" }, { status: 500 });\n }\n}\n\n/**\n * Push files to CDN (streaming version with progress)\n * Handles local files, remote files, and already-pushed files\n */\nexport async function handleSyncStream(request: Request) {\n const accountId = process.env.CLOUDFLARE_R2_ACCOUNT_ID;\n const accessKeyId = process.env.CLOUDFLARE_R2_ACCESS_KEY_ID;\n const secretAccessKey = process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY;\n const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;\n const publicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/\\s*$/, \"\");\n\n const encoder = new TextEncoder();\n\n const stream = new ReadableStream({\n async start(controller) {\n const sendEvent = (data: object) => {\n try {\n controller.enqueue(\n encoder.encode(`data: ${JSON.stringify(data)}\\n\\n`)\n );\n } catch {\n // Controller may be closed if client disconnected\n }\n };\n\n try {\n if (\n !accountId ||\n !accessKeyId ||\n !secretAccessKey ||\n !bucketName ||\n !publicUrl\n ) {\n sendEvent({\n type: \"error\",\n message:\n \"R2 not configured. Set CLOUDFLARE_R2_* environment variables.\",\n });\n controller.close();\n return;\n }\n\n const { imageKeys, operationId } = (await request.json()) as {\n imageKeys: string[];\n operationId?: string;\n };\n\n if (!imageKeys || !Array.isArray(imageKeys) || imageKeys.length === 0) {\n sendEvent({ type: \"error\", message: \"No image keys provided\" });\n controller.close();\n return;\n }\n\n // Helper to check if operation was cancelled\n const isCancelled = () =>\n operationId ? isOperationCancelled(operationId) : false;\n\n const meta = await loadMeta();\n const cdnUrls = getCdnUrls(meta);\n const cdnIndex = getOrAddCdnIndex(meta, publicUrl);\n\n const r2 = new S3Client({\n region: \"auto\",\n endpoint: `https://${accountId}.r2.cloudflarestorage.com`,\n credentials: { accessKeyId, secretAccessKey },\n });\n\n const pushed: string[] = [];\n const alreadyPushed: string[] = [];\n const errors: string[] = [];\n const sourceFolders = new Set<string>();\n const total = imageKeys.length;\n\n sendEvent({ type: \"start\", total });\n\n for (let i = 0; i < imageKeys.length; i++) {\n // Check for cancellation before each file\n if (isCancelled()) {\n await saveMeta(meta);\n // Clean up empty folders\n for (const folder of sourceFolders) {\n await deleteEmptyFolders(folder);\n }\n if (operationId) clearCancelledOperation(operationId);\n sendEvent({\n type: \"complete\",\n pushed: pushed.length,\n alreadyPushed: alreadyPushed.length,\n errors: errors.length,\n message: `Stopped. ${pushed.length} file${\n pushed.length !== 1 ? \"s\" : \"\"\n } pushed.`,\n cancelled: true,\n });\n controller.close();\n return;\n }\n\n let imageKey = imageKeys[i];\n // Normalize key to have leading /\n if (!imageKey.startsWith(\"/\")) {\n imageKey = `/${imageKey}`;\n }\n\n const entry = getMetaEntry(meta, imageKey);\n if (!entry) {\n errors.push(\n `Image not found in meta: ${imageKey}. Run Scan first.`\n );\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n pushed: pushed.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: path.basename(imageKey),\n });\n continue;\n }\n\n // Check if already pushed to our R2\n const existingCdnUrl =\n entry.c !== undefined ? cdnUrls[entry.c] : undefined;\n const isAlreadyInOurR2 = existingCdnUrl === publicUrl;\n\n if (isAlreadyInOurR2) {\n alreadyPushed.push(imageKey);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n pushed: pushed.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: path.basename(imageKey),\n });\n continue;\n }\n\n // Check if this is a remote image (in another CDN)\n const isRemote =\n entry.c !== undefined && existingCdnUrl !== publicUrl;\n\n try {\n let originalBuffer: Buffer;\n\n if (isRemote) {\n // Download from remote URL\n const remoteUrl = `${existingCdnUrl}${imageKey}`;\n originalBuffer = await downloadFromRemoteUrl(remoteUrl);\n } else {\n // Read from local file\n const originalLocalPath = getPublicPath(imageKey);\n try {\n originalBuffer = await fs.readFile(originalLocalPath);\n } catch {\n errors.push(`Original file not found: ${imageKey}`);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n pushed: pushed.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: path.basename(imageKey),\n });\n continue;\n }\n }\n\n // Upload original to R2\n await r2.send(\n new PutObjectCommand({\n Bucket: bucketName,\n Key: imageKey.replace(/^\\//, \"\"),\n Body: originalBuffer,\n ContentType: getContentType(imageKey),\n })\n );\n\n // Upload thumbnails (only if processed locally, not for remote imports)\n if (!isRemote && isProcessed(entry)) {\n for (const thumbPath of getAllThumbnailPaths(imageKey)) {\n const localPath = getPublicPath(thumbPath);\n try {\n const fileBuffer = await fs.readFile(localPath);\n await r2.send(\n new PutObjectCommand({\n Bucket: bucketName,\n Key: thumbPath.replace(/^\\//, \"\"),\n Body: fileBuffer,\n ContentType: getContentType(thumbPath),\n })\n );\n } catch {\n // Thumbnail might not exist\n }\n }\n }\n\n entry.c = cdnIndex;\n\n // Delete local files (only for non-remote, local images being pushed)\n if (!isRemote) {\n const originalLocalPath = getPublicPath(imageKey);\n\n // Track source folder for cleanup\n sourceFolders.add(path.dirname(originalLocalPath));\n\n // Delete local thumbnails\n for (const thumbPath of getAllThumbnailPaths(imageKey)) {\n const localPath = getPublicPath(thumbPath);\n sourceFolders.add(path.dirname(localPath));\n try {\n await fs.unlink(localPath);\n } catch {\n /* ignore */\n }\n }\n\n // Delete local original\n try {\n await fs.unlink(originalLocalPath);\n } catch {\n /* ignore */\n }\n }\n\n // Save meta after each successful push\n await saveMeta(meta);\n\n pushed.push(imageKey);\n } catch (error) {\n console.error(`Failed to push ${imageKey}:`, error);\n errors.push(`Failed to push: ${imageKey}`);\n }\n\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n pushed: pushed.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: path.basename(imageKey),\n });\n }\n\n // Clean up empty source folders\n for (const folder of sourceFolders) {\n await deleteEmptyFolders(folder);\n }\n\n // Build completion message\n let message: string | undefined;\n if (pushed.length === 0 && errors.length === 0) {\n message = `${alreadyPushed.length} file${\n alreadyPushed.length !== 1 ? \"s\" : \"\"\n } already on CDN. 0 new files pushed.`;\n } else if (alreadyPushed.length > 0 && errors.length === 0) {\n message = `${pushed.length} file${\n pushed.length !== 1 ? \"s\" : \"\"\n } pushed. ${alreadyPushed.length} already on CDN.`;\n }\n\n if (operationId) clearCancelledOperation(operationId);\n sendEvent({\n type: \"complete\",\n pushed: pushed.length,\n alreadyPushed: alreadyPushed.length,\n errors: errors.length,\n errorMessages: errors.length > 0 ? errors : undefined,\n message,\n });\n } catch (error) {\n console.error(\"Failed to push:\", error);\n sendEvent({ type: \"error\", message: \"Failed to push to CDN\" });\n } finally {\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n}\n\nexport async function handleReprocess(request: Request) {\n const publicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/\\s*$/, \"\");\n\n try {\n const { imageKeys } = (await request.json()) as { imageKeys: string[] };\n\n if (!imageKeys || !Array.isArray(imageKeys) || imageKeys.length === 0) {\n return jsonResponse({ error: \"No image keys provided\" }, { status: 400 });\n }\n\n const meta = await loadMeta();\n const cdnUrls = getCdnUrls(meta);\n const processed: string[] = [];\n const errors: string[] = [];\n\n for (let imageKey of imageKeys) {\n // Normalize key to have leading /\n if (!imageKey.startsWith(\"/\")) {\n imageKey = `/${imageKey}`;\n }\n\n try {\n let buffer: Buffer;\n const entry = getMetaEntry(meta, imageKey);\n const existingCdnIndex = entry?.c;\n const existingCdnUrl =\n existingCdnIndex !== undefined\n ? cdnUrls[existingCdnIndex]\n : undefined;\n\n // Determine if this is our R2 or a remote CDN\n const isInOurR2 = existingCdnUrl === publicUrl;\n const isRemote = existingCdnIndex !== undefined && !isInOurR2;\n\n const originalPath = getPublicPath(imageKey);\n\n try {\n buffer = await fs.readFile(originalPath);\n } catch {\n if (isInOurR2) {\n // Download original from our R2\n buffer = await downloadFromCdn(imageKey);\n const dir = path.dirname(originalPath);\n await fs.mkdir(dir, { recursive: true });\n await fs.writeFile(originalPath, buffer);\n } else if (isRemote && existingCdnUrl) {\n // Download from remote URL\n const remoteUrl = `${existingCdnUrl}${imageKey}`;\n buffer = await downloadFromRemoteUrl(remoteUrl);\n const dir = path.dirname(originalPath);\n await fs.mkdir(dir, { recursive: true });\n await fs.writeFile(originalPath, buffer);\n } else {\n throw new Error(`File not found: ${imageKey}`);\n }\n }\n\n const updatedEntry = await processImage(buffer, imageKey);\n // No need to set p flag - presence of thumbnail dims (sm/md/lg/f) indicates processed\n\n if (isInOurR2) {\n // Re-upload to R2 and clean up local files\n updatedEntry.c = existingCdnIndex;\n // Delete original and thumbnails from CDN first to clear cache\n await deleteOriginalFromCdn(imageKey);\n await deleteThumbnailsFromCdn(imageKey);\n // Re-upload original and thumbnails\n await uploadOriginalToCdn(imageKey);\n await uploadToCdn(imageKey);\n\n await deleteLocalThumbnails(imageKey);\n // Delete local original\n try {\n await fs.unlink(originalPath);\n } catch {\n /* ignore */\n }\n } else if (isRemote) {\n // Remote image processed locally - remove c flag, now it's local\n // Keep the original and thumbnails locally\n }\n\n meta[imageKey] = updatedEntry;\n processed.push(imageKey);\n } catch (error) {\n console.error(`Failed to reprocess ${imageKey}:`, error);\n errors.push(imageKey);\n }\n }\n\n await saveMeta(meta);\n\n return jsonResponse({\n success: true,\n processed,\n errors: errors.length > 0 ? errors : undefined,\n });\n } catch (error) {\n console.error(\"Failed to reprocess:\", error);\n return jsonResponse(\n { error: \"Failed to reprocess images\" },\n { status: 500 }\n );\n }\n}\n\nexport async function handleUnprocessStream(request: Request) {\n const publicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/\\s*$/, \"\");\n const encoder = new TextEncoder();\n\n // Parse the request body before creating the stream\n let imageKeys: string[];\n let operationId: string | undefined;\n try {\n const body = (await request.json()) as {\n imageKeys: string[];\n operationId?: string;\n };\n imageKeys = body.imageKeys;\n operationId = body.operationId;\n\n if (!imageKeys || !Array.isArray(imageKeys) || imageKeys.length === 0) {\n return jsonResponse({ error: \"No image keys provided\" }, { status: 400 });\n }\n } catch {\n return jsonResponse({ error: \"Invalid request body\" }, { status: 400 });\n }\n\n // Helper to check if operation was cancelled\n const isCancelled = () =>\n operationId ? isOperationCancelled(operationId) : false;\n\n const stream = new ReadableStream({\n async start(controller) {\n const sendEvent = (data: object) => {\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\\n\\n`));\n };\n\n try {\n const meta = await loadMeta();\n const cdnUrls = getCdnUrls(meta);\n const removed: string[] = [];\n const skipped: string[] = [];\n const errors: string[] = [];\n\n const total = imageKeys.length;\n sendEvent({ type: \"start\", total });\n\n for (let i = 0; i < imageKeys.length; i++) {\n // Check for cancellation before each image\n if (isCancelled()) {\n await saveMeta(meta);\n if (operationId) clearCancelledOperation(operationId);\n sendEvent({\n type: \"complete\",\n processed: removed.length,\n errors: errors.length,\n message: `Stopped. Removed thumbnails for ${\n removed.length\n } image${removed.length !== 1 ? \"s\" : \"\"}.`,\n cancelled: true,\n });\n controller.close();\n return;\n }\n\n let imageKey = imageKeys[i];\n\n // Normalize key to have leading /\n if (!imageKey.startsWith(\"/\")) {\n imageKey = `/${imageKey}`;\n }\n\n try {\n const entry = getMetaEntry(meta, imageKey);\n if (!entry) {\n errors.push(imageKey);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n processed: removed.length,\n percent: Math.round(((i + 1) / total) * 100),\n message: `Error: ${imageKey.slice(1)}`,\n });\n continue;\n }\n\n // Check if this image has any thumbnails\n const hasThumbnails = entry.sm || entry.md || entry.lg || entry.f;\n if (!hasThumbnails) {\n skipped.push(imageKey);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n processed: removed.length,\n percent: Math.round(((i + 1) / total) * 100),\n message: `Skipped ${imageKey.slice(1)} (no thumbnails)`,\n });\n continue;\n }\n\n const existingCdnIndex = entry.c;\n const existingCdnUrl =\n existingCdnIndex !== undefined\n ? cdnUrls[existingCdnIndex]\n : undefined;\n const isInOurR2 = existingCdnUrl === publicUrl;\n\n // Delete local thumbnails\n await deleteLocalThumbnails(imageKey);\n\n // Delete cloud thumbnails if in our R2\n if (isInOurR2) {\n await deleteThumbnailsFromCdn(imageKey);\n }\n\n // Update meta - keep o, b, c but remove thumbnail dimensions\n meta[imageKey] = {\n o: entry.o,\n b: entry.b,\n ...(entry.c !== undefined ? { c: entry.c } : {}),\n };\n\n // Save meta after each successful removal\n await saveMeta(meta);\n\n removed.push(imageKey);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n processed: removed.length,\n percent: Math.round(((i + 1) / total) * 100),\n message: `Removed thumbnails for ${imageKey.slice(1)}`,\n });\n } catch (error) {\n console.error(`Failed to unprocess ${imageKey}:`, error);\n errors.push(imageKey);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n processed: removed.length,\n percent: Math.round(((i + 1) / total) * 100),\n message: `Failed: ${imageKey.slice(1)}`,\n });\n }\n }\n\n sendEvent({ type: \"cleanup\", message: \"Saving metadata...\" });\n await saveMeta(meta);\n\n // Clean up empty folders in the images directory\n sendEvent({ type: \"cleanup\", message: \"Cleaning up empty folders...\" });\n\n const imagesDir = getPublicPath(\"images\");\n try {\n await cleanupEmptyFoldersRecursive(imagesDir);\n } catch {\n // images dir might not exist\n }\n\n // Build completion message\n let message = `Removed thumbnails from ${removed.length} image${\n removed.length !== 1 ? \"s\" : \"\"\n }.`;\n if (skipped.length > 0) {\n message += ` ${skipped.length} image${\n skipped.length !== 1 ? \"s\" : \"\"\n } had no thumbnails.`;\n }\n if (errors.length > 0) {\n message += ` ${errors.length} image${\n errors.length !== 1 ? \"s\" : \"\"\n } failed.`;\n }\n\n sendEvent({\n type: \"complete\",\n processed: removed.length,\n skipped: skipped.length,\n errors: errors.length,\n message,\n });\n\n controller.close();\n } catch (error) {\n console.error(\"Unprocess stream error:\", error);\n sendEvent({ type: \"error\", message: \"Failed to remove thumbnails\" });\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n}\n\nexport async function handleReprocessStream(request: Request) {\n const publicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/\\s*$/, \"\");\n const encoder = new TextEncoder();\n\n // Parse the request body before creating the stream\n let imageKeys: string[];\n let operationId: string | undefined;\n try {\n const body = (await request.json()) as {\n imageKeys: string[];\n operationId?: string;\n };\n imageKeys = body.imageKeys;\n operationId = body.operationId;\n\n if (!imageKeys || !Array.isArray(imageKeys) || imageKeys.length === 0) {\n return jsonResponse({ error: \"No image keys provided\" }, { status: 400 });\n }\n } catch {\n return jsonResponse({ error: \"Invalid request body\" }, { status: 400 });\n }\n\n // Helper to check if operation was cancelled\n const isCancelled = () =>\n operationId ? isOperationCancelled(operationId) : false;\n\n const stream = new ReadableStream({\n async start(controller) {\n const sendEvent = (data: object) => {\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\\n\\n`));\n };\n\n try {\n const meta = await loadMeta();\n const cdnUrls = getCdnUrls(meta);\n const processed: string[] = [];\n const errors: string[] = [];\n\n const total = imageKeys.length;\n sendEvent({ type: \"start\", total });\n\n for (let i = 0; i < imageKeys.length; i++) {\n // Check for cancellation before each image\n if (isCancelled()) {\n await saveMeta(meta);\n if (operationId) clearCancelledOperation(operationId);\n sendEvent({\n type: \"complete\",\n processed: processed.length,\n errors: errors.length,\n message: `Stopped. Generated thumbnails for ${\n processed.length\n } image${processed.length !== 1 ? \"s\" : \"\"}.`,\n cancelled: true,\n });\n controller.close();\n return;\n }\n\n let imageKey = imageKeys[i];\n\n // Normalize key to have leading /\n if (!imageKey.startsWith(\"/\")) {\n imageKey = `/${imageKey}`;\n }\n\n try {\n let buffer: Buffer;\n const entry = getMetaEntry(meta, imageKey);\n const existingCdnIndex = entry?.c;\n const existingCdnUrl =\n existingCdnIndex !== undefined\n ? cdnUrls[existingCdnIndex]\n : undefined;\n\n // Determine if this is our R2 or a remote CDN\n const isInOurR2 = existingCdnUrl === publicUrl;\n const isRemote = existingCdnIndex !== undefined && !isInOurR2;\n\n const originalPath = getPublicPath(imageKey);\n\n try {\n buffer = await fs.readFile(originalPath);\n } catch {\n if (isInOurR2) {\n buffer = await downloadFromCdn(imageKey);\n const dir = path.dirname(originalPath);\n await fs.mkdir(dir, { recursive: true });\n await fs.writeFile(originalPath, buffer);\n } else if (isRemote && existingCdnUrl) {\n const remoteUrl = `${existingCdnUrl}${imageKey}`;\n buffer = await downloadFromRemoteUrl(remoteUrl);\n const dir = path.dirname(originalPath);\n await fs.mkdir(dir, { recursive: true });\n await fs.writeFile(originalPath, buffer);\n } else {\n throw new Error(`File not found: ${imageKey}`);\n }\n }\n\n const ext = path.extname(imageKey).toLowerCase();\n const isSvg = ext === \".svg\";\n\n if (isSvg) {\n const imageDir = path.dirname(imageKey.slice(1));\n const imagesPath = getPublicPath(\n \"images\",\n imageDir === \".\" ? \"\" : imageDir\n );\n await fs.mkdir(imagesPath, { recursive: true });\n\n const fileName = path.basename(imageKey);\n const destPath = path.join(imagesPath, fileName);\n await fs.writeFile(destPath, buffer);\n\n meta[imageKey] = {\n ...entry,\n o: { w: 0, h: 0 },\n b: \"\",\n f: { w: 0, h: 0 },\n };\n\n if (isRemote) {\n delete (meta[imageKey] as import(\"../types\").MetaEntry).c;\n }\n } else {\n const updatedEntry = await processImage(buffer, imageKey);\n\n if (isInOurR2) {\n updatedEntry.c = existingCdnIndex;\n // Delete original and thumbnails from CDN first to clear cache\n await deleteOriginalFromCdn(imageKey);\n await deleteThumbnailsFromCdn(imageKey);\n // Re-upload original and thumbnails\n await uploadOriginalToCdn(imageKey);\n await uploadToCdn(imageKey);\n\n await deleteLocalThumbnails(imageKey);\n try {\n await fs.unlink(originalPath);\n } catch {\n /* ignore */\n }\n }\n\n meta[imageKey] = updatedEntry;\n }\n\n // Save meta after each successful process\n await saveMeta(meta);\n\n processed.push(imageKey);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n processed: processed.length,\n percent: Math.round(((i + 1) / total) * 100),\n message: `Processed ${imageKey.slice(1)}`,\n });\n } catch (error) {\n console.error(`Failed to reprocess ${imageKey}:`, error);\n errors.push(imageKey);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n processed: processed.length,\n percent: Math.round(((i + 1) / total) * 100),\n message: `Failed: ${imageKey.slice(1)}`,\n });\n }\n }\n\n sendEvent({ type: \"cleanup\", message: \"Saving metadata...\" });\n await saveMeta(meta);\n\n // Build completion message\n let message = `Generated thumbnails for ${processed.length} image${\n processed.length !== 1 ? \"s\" : \"\"\n }.`;\n if (errors.length > 0) {\n message += ` ${errors.length} image${\n errors.length !== 1 ? \"s\" : \"\"\n } failed.`;\n }\n\n sendEvent({\n type: \"complete\",\n processed: processed.length,\n errors: errors.length,\n message,\n });\n\n controller.close();\n } catch (error) {\n console.error(\"Reprocess stream error:\", error);\n sendEvent({ type: \"error\", message: \"Failed to generate thumbnails\" });\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n}\n\nexport async function handleProcessAllStream() {\n const publicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/\\s*$/, \"\");\n const encoder = new TextEncoder();\n\n const stream = new ReadableStream({\n async start(controller) {\n const sendEvent = (data: object) => {\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\\n\\n`));\n };\n\n try {\n const meta = await loadMeta();\n const cdnUrls = getCdnUrls(meta);\n const processed: string[] = [];\n const errors: string[] = [];\n const orphansRemoved: string[] = [];\n\n // Count images in different states\n let alreadyProcessed = 0;\n\n // Get all images from meta that need processing (no p flag = not processed yet)\n const imagesToProcess: Array<{\n key: string;\n entry: import(\"../types\").MetaEntry;\n }> = [];\n\n for (const [key, entry] of getFileEntries(meta)) {\n const fileName = path.basename(key);\n if (!isImageFile(fileName)) continue;\n\n // Check if needs processing (no thumbnail dims = not processed yet)\n if (!isProcessed(entry)) {\n imagesToProcess.push({ key, entry });\n } else {\n alreadyProcessed++;\n }\n }\n\n const total = imagesToProcess.length;\n sendEvent({ type: \"start\", total });\n\n for (let i = 0; i < imagesToProcess.length; i++) {\n const { key, entry } = imagesToProcess[i];\n const fullPath = getPublicPath(key);\n const existingCdnIndex = entry.c;\n const existingCdnUrl =\n existingCdnIndex !== undefined\n ? cdnUrls[existingCdnIndex]\n : undefined;\n\n // Determine if this is our R2 or a remote CDN\n const isInOurR2 = existingCdnUrl === publicUrl;\n const isRemote = existingCdnIndex !== undefined && !isInOurR2;\n\n try {\n let buffer: Buffer;\n\n // Download from appropriate source\n if (isInOurR2) {\n buffer = await downloadFromCdn(key);\n const dir = path.dirname(fullPath);\n await fs.mkdir(dir, { recursive: true });\n await fs.writeFile(fullPath, buffer);\n } else if (isRemote && existingCdnUrl) {\n const remoteUrl = `${existingCdnUrl}${key}`;\n buffer = await downloadFromRemoteUrl(remoteUrl);\n const dir = path.dirname(fullPath);\n await fs.mkdir(dir, { recursive: true });\n await fs.writeFile(fullPath, buffer);\n } else {\n buffer = await fs.readFile(fullPath);\n }\n\n const ext = path.extname(key).toLowerCase();\n const isSvg = ext === \".svg\";\n\n if (isSvg) {\n const imageDir = path.dirname(key.slice(1));\n const imagesPath = getPublicPath(\n \"images\",\n imageDir === \".\" ? \"\" : imageDir\n );\n await fs.mkdir(imagesPath, { recursive: true });\n\n const fileName = path.basename(key);\n const destPath = path.join(imagesPath, fileName);\n await fs.writeFile(destPath, buffer);\n\n meta[key] = {\n ...entry,\n o: { w: 0, h: 0 },\n b: \"\",\n f: { w: 0, h: 0 }, // SVG has \"full\" to indicate processed\n };\n\n // Remote images become local after processing\n if (isRemote) {\n delete (meta[key] as import(\"../types\").MetaEntry).c;\n }\n } else {\n const processedEntry = await processImage(buffer, key);\n meta[key] = {\n ...processedEntry,\n ...(isInOurR2 ? { c: existingCdnIndex } : {}),\n };\n // Remote images become local after processing (no c)\n }\n\n // If image was in our R2, re-upload original + thumbnails and clean up local files\n if (isInOurR2) {\n // Delete original and thumbnails from CDN first to clear cache\n await deleteOriginalFromCdn(key);\n await deleteThumbnailsFromCdn(key);\n // Re-upload original and thumbnails\n await uploadOriginalToCdn(key);\n await uploadToCdn(key);\n\n await deleteLocalThumbnails(key);\n // Delete local original\n try {\n await fs.unlink(fullPath);\n } catch {\n /* ignore */\n }\n }\n // Remote images stay local after processing (original + thumbnails)\n\n processed.push(key.slice(1));\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n processed: processed.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: key.slice(1),\n });\n } catch (error) {\n console.error(`Failed to process ${key}:`, error);\n errors.push(key.slice(1));\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n processed: processed.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: key.slice(1),\n message: `Failed: ${key.slice(1)}`,\n });\n }\n }\n\n sendEvent({\n type: \"cleanup\",\n message: \"Removing orphaned thumbnails...\",\n });\n\n // Build set of expected thumbnail paths\n const trackedPaths = new Set<string>();\n for (const [imageKey, entry] of getFileEntries(meta)) {\n // Only track local thumbnails (not pushed to CDN)\n if (entry.c === undefined) {\n for (const thumbPath of getAllThumbnailPaths(imageKey)) {\n trackedPaths.add(thumbPath);\n }\n }\n }\n\n async function findOrphans(\n dir: string,\n relativePath: string = \"\"\n ): Promise<void> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n\n for (const fsEntry of entries) {\n if (fsEntry.name.startsWith(\".\")) continue;\n\n const entryFullPath = path.join(dir, fsEntry.name);\n const relPath = relativePath\n ? `${relativePath}/${fsEntry.name}`\n : fsEntry.name;\n\n if (fsEntry.isDirectory()) {\n await findOrphans(entryFullPath, relPath);\n } else if (isImageFile(fsEntry.name)) {\n const publicPath = `/images/${relPath}`;\n if (!trackedPaths.has(publicPath)) {\n try {\n await fs.unlink(entryFullPath);\n orphansRemoved.push(publicPath);\n } catch (err) {\n console.error(\n `Failed to remove orphan ${publicPath}:`,\n err\n );\n }\n }\n }\n }\n } catch {\n // Directory might not exist\n }\n }\n\n const imagesDir = getPublicPath(\"images\");\n try {\n await findOrphans(imagesDir);\n } catch {\n // images dir might not exist\n }\n\n async function removeEmptyDirs(dir: string): Promise<boolean> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n let isEmpty = true;\n\n for (const fsEntry of entries) {\n if (fsEntry.isDirectory()) {\n const subDirEmpty = await removeEmptyDirs(\n path.join(dir, fsEntry.name)\n );\n if (!subDirEmpty) isEmpty = false;\n } else {\n isEmpty = false;\n }\n }\n\n if (isEmpty && dir !== imagesDir) {\n await fs.rmdir(dir);\n }\n\n return isEmpty;\n } catch {\n return true;\n }\n }\n\n try {\n await removeEmptyDirs(imagesDir);\n } catch {\n // images dir might not exist\n }\n\n await saveMeta(meta);\n\n sendEvent({\n type: \"complete\",\n processed: processed.length,\n alreadyProcessed,\n orphansRemoved: orphansRemoved.length,\n errors: errors.length,\n });\n } catch (error) {\n console.error(\"Failed to process all:\", error);\n sendEvent({ type: \"error\", message: \"Failed to process images\" });\n } finally {\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n}\n\n/**\n * Download images from R2 CDN to local storage (streaming)\n * This removes the images from R2 and stores them locally\n */\nexport async function handleDownloadStream(request: Request) {\n const { imageKeys, operationId } = (await request.json()) as {\n imageKeys: string[];\n operationId?: string;\n };\n\n if (!imageKeys || !Array.isArray(imageKeys) || imageKeys.length === 0) {\n return jsonResponse({ error: \"No image keys provided\" }, { status: 400 });\n }\n\n const stream = new ReadableStream({\n async start(controller) {\n const encoder = new TextEncoder();\n const sendEvent = (data: Record<string, unknown>) => {\n try {\n controller.enqueue(\n encoder.encode(`data: ${JSON.stringify(data)}\\n\\n`)\n );\n } catch {\n // Controller may be closed if client disconnected\n }\n };\n\n sendEvent({ type: \"start\", total: imageKeys.length });\n\n const downloaded: string[] = [];\n const skipped: string[] = [];\n const errors: string[] = [];\n\n // Helper to check if operation was cancelled\n const isCancelled = () =>\n operationId ? isOperationCancelled(operationId) : false;\n\n try {\n const meta = await loadMeta();\n\n for (let i = 0; i < imageKeys.length; i++) {\n // Check if operation was cancelled\n if (isCancelled()) {\n // Save meta with what we've done so far\n await saveMeta(meta);\n if (operationId) clearCancelledOperation(operationId);\n sendEvent({\n type: \"complete\",\n downloaded: downloaded.length,\n message: `Stopped. ${downloaded.length} image${\n downloaded.length !== 1 ? \"s\" : \"\"\n } downloaded.`,\n cancelled: true,\n });\n controller.close();\n return;\n }\n\n const imageKey = imageKeys[i];\n const entry = getMetaEntry(meta, imageKey);\n\n if (!entry || entry.c === undefined) {\n skipped.push(imageKey);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total: imageKeys.length,\n downloaded: downloaded.length,\n message: `Skipped ${imageKey} (not on cloud)`,\n });\n continue;\n }\n\n try {\n // Download original from R2\n const imageBuffer = await downloadFromCdn(imageKey);\n\n // Check again after download (long operation)\n if (isCancelled()) {\n await saveMeta(meta);\n if (operationId) clearCancelledOperation(operationId);\n sendEvent({\n type: \"complete\",\n downloaded: downloaded.length,\n message: `Stopped. ${downloaded.length} image${\n downloaded.length !== 1 ? \"s\" : \"\"\n } downloaded.`,\n cancelled: true,\n });\n controller.close();\n return;\n }\n\n // Ensure directory exists\n const localPath = getPublicPath(imageKey.replace(/^\\//, \"\"));\n await fs.mkdir(path.dirname(localPath), { recursive: true });\n\n // Write to local filesystem\n await fs.writeFile(localPath, imageBuffer);\n\n // Delete original and thumbnails from R2\n await deleteOriginalFromCdn(imageKey);\n await deleteThumbnailsFromCdn(imageKey);\n\n // Check if image was processed (has thumbnails)\n const wasProcessed = isProcessed(entry);\n\n // Remove the c property (no longer on CDN)\n delete entry.c;\n\n // If it was processed, regenerate thumbnails locally\n if (wasProcessed) {\n const processedEntry = await processImage(imageBuffer, imageKey);\n // Update dimensions in meta\n entry.sm = processedEntry.sm;\n entry.md = processedEntry.md;\n entry.lg = processedEntry.lg;\n entry.f = processedEntry.f;\n }\n\n // Save meta after each successful download\n await saveMeta(meta);\n\n downloaded.push(imageKey);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total: imageKeys.length,\n downloaded: downloaded.length,\n message: `Downloaded ${imageKey}`,\n });\n } catch (error) {\n console.error(`Failed to download ${imageKey}:`, error);\n errors.push(imageKey);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total: imageKeys.length,\n downloaded: downloaded.length,\n message: `Failed to download ${imageKey}`,\n });\n }\n }\n\n await saveMeta(meta);\n\n // Build completion message\n let message = `Downloaded ${downloaded.length} image${\n downloaded.length !== 1 ? \"s\" : \"\"\n }.`;\n if (skipped.length > 0) {\n message += ` ${skipped.length} image${\n skipped.length !== 1 ? \"s were\" : \" was\"\n } not on cloud.`;\n }\n if (errors.length > 0) {\n message += ` ${errors.length} image${\n errors.length !== 1 ? \"s\" : \"\"\n } failed.`;\n }\n\n sendEvent({\n type: \"complete\",\n downloaded: downloaded.length,\n skipped: skipped.length,\n errors: errors.length,\n message,\n });\n } catch (error) {\n console.error(\"Download stream error:\", error);\n sendEvent({ type: \"error\", message: \"Failed to download images\" });\n } finally {\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n}\n\n/**\n * Push pending updates to cloud (replace cloud files with local versions)\n * Streaming handler for progress feedback\n */\nexport async function handlePushUpdatesStream(request: Request) {\n const accountId = process.env.CLOUDFLARE_R2_ACCOUNT_ID;\n const accessKeyId = process.env.CLOUDFLARE_R2_ACCESS_KEY_ID;\n const secretAccessKey = process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY;\n const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;\n const publicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/$/, \"\");\n\n const encoder = new TextEncoder();\n\n const stream = new ReadableStream({\n async start(controller) {\n const sendEvent = (data: object) => {\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\\n\\n`));\n };\n\n try {\n if (\n !accountId ||\n !accessKeyId ||\n !secretAccessKey ||\n !bucketName ||\n !publicUrl\n ) {\n sendEvent({ type: \"error\", message: \"R2 not configured\" });\n controller.close();\n return;\n }\n\n const { paths: inputPaths, operationId } = (await request.json()) as {\n paths: string[];\n operationId?: string;\n };\n\n if (\n !inputPaths ||\n !Array.isArray(inputPaths) ||\n inputPaths.length === 0\n ) {\n sendEvent({ type: \"error\", message: \"No paths provided\" });\n controller.close();\n return;\n }\n\n // Helper to check if operation was cancelled\n const isCancelled = () =>\n operationId ? isOperationCancelled(operationId) : false;\n\n const s3 = new S3Client({\n region: \"auto\",\n endpoint: `https://${accountId}.r2.cloudflarestorage.com`,\n credentials: { accessKeyId, secretAccessKey },\n });\n\n const meta = await loadMeta();\n const cdnUrls = getCdnUrls(meta);\n const r2PublicUrl = publicUrl.replace(/\\/$/, \"\");\n\n // Expand folder paths to individual file paths with pending updates\n const paths: string[] = [];\n for (const inputPath of inputPaths) {\n const key = inputPath.startsWith(\"public/\")\n ? \"/\" + inputPath.slice(7)\n : inputPath;\n // Check if this is a folder path (no extension or ends without extension-like pattern)\n const isFolder = !key.match(/\\.[a-zA-Z0-9]+$/);\n\n if (isFolder) {\n // Find all files in meta that start with this folder path and have u: 1\n const folderPrefix = key.endsWith(\"/\") ? key : key + \"/\";\n for (const [metaKey, entry] of Object.entries(meta)) {\n if (\n metaKey.startsWith(folderPrefix) &&\n entry &&\n typeof entry === \"object\" &&\n \"u\" in entry &&\n entry.u === 1\n ) {\n paths.push(metaKey);\n }\n }\n } else {\n paths.push(inputPath);\n }\n }\n\n const pushed: string[] = [];\n const skipped: string[] = [];\n const errors: string[] = [];\n const total = paths.length;\n\n if (total === 0) {\n sendEvent({\n type: \"complete\",\n pushed: 0,\n message: \"No files with pending updates found.\",\n });\n controller.close();\n return;\n }\n\n sendEvent({ type: \"start\", total });\n\n for (let i = 0; i < paths.length; i++) {\n // Check for cancellation before each file\n if (isCancelled()) {\n await saveMeta(meta);\n if (operationId) clearCancelledOperation(operationId);\n sendEvent({\n type: \"complete\",\n pushed: pushed.length,\n message: `Stopped. ${pushed.length} file${\n pushed.length !== 1 ? \"s\" : \"\"\n } pushed.`,\n cancelled: true,\n });\n controller.close();\n return;\n }\n const itemPath = paths[i];\n const key = itemPath.startsWith(\"public/\")\n ? \"/\" + itemPath.slice(7)\n : itemPath;\n const entry = meta[key] as MetaEntry | undefined;\n\n if (!entry || entry.u !== 1) {\n skipped.push(key);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n pushed: pushed.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: path.basename(key),\n });\n continue;\n }\n\n // Check if this is an R2 file\n const fileCdnUrl =\n entry.c !== undefined\n ? cdnUrls[entry.c]?.replace(/\\/$/, \"\")\n : undefined;\n if (!fileCdnUrl || fileCdnUrl !== r2PublicUrl) {\n skipped.push(key);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n pushed: pushed.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: path.basename(key),\n });\n continue;\n }\n\n try {\n // Read the local file\n const localPath = getPublicPath(key);\n const buffer = await fs.readFile(localPath);\n const contentType = getContentType(path.basename(key));\n\n // Delete from CDN first to clear cache\n const uploadKey = key.startsWith(\"/\") ? key.slice(1) : key;\n try {\n await s3.send(\n new DeleteObjectCommand({\n Bucket: bucketName,\n Key: uploadKey,\n })\n );\n } catch {\n // Ignore delete errors - file might not exist\n }\n\n // Upload to R2\n await s3.send(\n new PutObjectCommand({\n Bucket: bucketName,\n Key: uploadKey,\n Body: buffer,\n ContentType: contentType,\n })\n );\n\n // If image is processed, also update thumbnails\n if (isProcessed(entry)) {\n // Delete existing thumbnails from CDN first\n await deleteThumbnailsFromCdn(key);\n\n // Re-process to generate new thumbnails from local file\n const processedEntry = await processImage(buffer, key);\n Object.assign(entry, processedEntry);\n\n // Upload thumbnails\n await uploadToCdn(key);\n\n // Delete local thumbnails\n await deleteLocalThumbnails(key);\n }\n\n // Delete local file (it's now on cloud)\n await fs.unlink(localPath);\n\n // Remove the update flag\n delete entry.u;\n\n // Save meta after each successful push\n await saveMeta(meta);\n\n pushed.push(key);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n pushed: pushed.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: path.basename(key),\n });\n } catch (error) {\n console.error(`Failed to push update for ${key}:`, error);\n errors.push(key);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n pushed: pushed.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: path.basename(key),\n message: `Failed: ${path.basename(key)}`,\n });\n }\n }\n\n // Clean up empty folders\n sendEvent({ type: \"cleanup\", message: \"Cleaning up...\" });\n for (const itemPath of pushed) {\n const localPath = getPublicPath(itemPath);\n await deleteEmptyFolders(path.dirname(localPath));\n }\n\n await saveMeta(meta);\n\n let message = `Pushed ${pushed.length} update${\n pushed.length !== 1 ? \"s\" : \"\"\n } to cloud.`;\n if (skipped.length > 0) {\n message += ` ${skipped.length} file${\n skipped.length !== 1 ? \"s\" : \"\"\n } skipped.`;\n }\n if (errors.length > 0) {\n message += ` ${errors.length} file${\n errors.length !== 1 ? \"s\" : \"\"\n } failed.`;\n }\n\n sendEvent({\n type: \"complete\",\n pushed: pushed.length,\n skipped: skipped.length,\n errors: errors.length,\n message,\n });\n } catch (error) {\n console.error(\"Push updates error:\", error);\n sendEvent({ type: \"error\", message: \"Failed to push updates\" });\n } finally {\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n}\n\n/**\n * Cancel a streaming operation (download, push, etc.)\n */\nexport async function handleCancelStreamOperation(request: Request) {\n try {\n const { operationId } = await request.json();\n\n if (!operationId || typeof operationId !== \"string\") {\n return jsonResponse(\n { error: \"No operation ID provided\" },\n { status: 400 }\n );\n }\n\n cancelOperation(operationId);\n\n return jsonResponse({ success: true, operationId });\n } catch (error) {\n console.error(\"Failed to cancel operation:\", error);\n return jsonResponse(\n { error: \"Failed to cancel operation\" },\n { status: 500 }\n );\n }\n}\n\n/**\n * Cancel pending updates (delete local files, keep cloud versions)\n */\nexport async function handleCancelUpdates(request: Request) {\n try {\n const { paths: inputPaths } = await request.json();\n\n if (!inputPaths || !Array.isArray(inputPaths) || inputPaths.length === 0) {\n return jsonResponse({ error: \"No paths provided\" }, { status: 400 });\n }\n\n const meta = await loadMeta();\n\n // Expand folder paths to individual file paths with pending updates\n const paths: string[] = [];\n for (const inputPath of inputPaths) {\n const key = inputPath.startsWith(\"public/\")\n ? \"/\" + inputPath.slice(7)\n : inputPath;\n // Check if this is a folder path (no extension or ends without extension-like pattern)\n const isFolder = !key.match(/\\.[a-zA-Z0-9]+$/);\n\n if (isFolder) {\n // Find all files in meta that start with this folder path and have u: 1\n const folderPrefix = key.endsWith(\"/\") ? key : key + \"/\";\n for (const [metaKey, entry] of Object.entries(meta)) {\n if (\n metaKey.startsWith(folderPrefix) &&\n entry &&\n typeof entry === \"object\" &&\n \"u\" in entry &&\n entry.u === 1\n ) {\n paths.push(metaKey);\n }\n }\n } else {\n paths.push(inputPath);\n }\n }\n\n const cancelled: string[] = [];\n const skipped: string[] = [];\n const errors: string[] = [];\n const foldersToClean = new Set<string>();\n\n for (const itemPath of paths) {\n const key = itemPath.startsWith(\"public/\")\n ? \"/\" + itemPath.slice(7)\n : itemPath;\n const entry = meta[key] as { u?: 1 } | undefined;\n\n if (!entry || entry.u !== 1) {\n skipped.push(key);\n continue;\n }\n\n try {\n // Delete the local file\n const localPath = getPublicPath(key);\n await fs.unlink(localPath);\n\n // Track folder for cleanup\n foldersToClean.add(path.dirname(localPath));\n\n // Remove the update flag\n delete entry.u;\n\n cancelled.push(key);\n } catch (error) {\n console.error(`Failed to cancel update for ${key}:`, error);\n errors.push(key);\n }\n }\n\n // Clean up empty folders\n for (const folder of foldersToClean) {\n await deleteEmptyFolders(folder);\n }\n\n await saveMeta(meta);\n\n return jsonResponse({\n success: true,\n cancelled: cancelled.length,\n skipped: skipped.length,\n errors: errors.length,\n });\n } catch (error) {\n console.error(\"Cancel updates error:\", error);\n return jsonResponse({ error: \"Failed to cancel updates\" }, { status: 500 });\n }\n}\n","import { promises as fs } from \"fs\";\nimport path from \"path\";\nimport sharp from \"sharp\";\nimport { jsonResponse } from \"./utils/response\";\nimport {\n loadMeta,\n saveMeta,\n isMediaFile,\n isImageFile,\n getFileEntries,\n slugifyFilename,\n} from \"./utils\";\nimport { getAllThumbnailPaths, isProcessed } from \"../types\";\nimport { getPublicPath } from \"../config\";\nimport {\n deleteEmptyFolders,\n cleanupEmptyFoldersRecursive,\n} from \"./utils/folders\";\n\n/**\n * Streaming scan handler - scans filesystem for new files not in meta\n * For images, reads dimensions (w/h)\n * Handles collisions by renaming files with -1, -2, etc.\n * Also detects orphaned files in the images folder\n */\nexport async function handleScanStream() {\n const encoder = new TextEncoder();\n\n const stream = new ReadableStream({\n async start(controller) {\n const sendEvent = (data: object) => {\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\\n\\n`));\n };\n\n try {\n const meta = await loadMeta();\n const existingCount = Object.keys(meta).filter(\n (k) => !k.startsWith(\"_\")\n ).length;\n const existingKeys = new Set(Object.keys(meta));\n const added: string[] = [];\n const renamed: Array<{ from: string; to: string }> = [];\n const errors: string[] = [];\n const orphanedFiles: string[] = [];\n const pendingUpdates: string[] = []; // Files that override cloud files\n\n // Collect all files first\n const allFiles: Array<{ relativePath: string; fullPath: string }> = [];\n\n async function scanDir(\n dir: string,\n relativePath: string = \"\"\n ): Promise<void> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.name.startsWith(\".\")) continue;\n\n const fullPath = path.join(dir, entry.name);\n const relPath = relativePath\n ? `${relativePath}/${entry.name}`\n : entry.name;\n\n // Skip the images folder (generated thumbnails)\n if (relPath === \"images\" || relPath.startsWith(\"images/\"))\n continue;\n\n if (entry.isDirectory()) {\n await scanDir(fullPath, relPath);\n } else if (isMediaFile(entry.name)) {\n allFiles.push({ relativePath: relPath, fullPath });\n }\n }\n } catch {\n // Directory might not exist\n }\n }\n\n const publicDir = getPublicPath();\n await scanDir(publicDir);\n\n const total = allFiles.length;\n sendEvent({ type: \"start\", total });\n\n for (let i = 0; i < allFiles.length; i++) {\n let { relativePath, fullPath } = allFiles[i];\n let imageKey = \"/\" + relativePath;\n\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: relativePath,\n });\n\n // Check if already in meta\n if (existingKeys.has(imageKey)) {\n // Check if this is a cloud file with a local override\n const entry = meta[imageKey] as { c?: number; u?: 1 } | undefined;\n if (entry?.c !== undefined && !entry?.u) {\n // This is a cloud file - local file is an override\n // Mark as pending update\n entry.u = 1;\n pendingUpdates.push(imageKey);\n }\n // File already tracked - skip adding\n continue;\n }\n\n // Slugify filename to be URL-safe (lowercase, no spaces, etc.)\n const dirName = path.dirname(relativePath);\n const originalFileName = path.basename(relativePath);\n const sluggedFileName = slugifyFilename(originalFileName);\n\n // Check if filename needs to be slugified\n if (sluggedFileName !== originalFileName) {\n const newRelativePath =\n dirName === \".\"\n ? sluggedFileName\n : `${dirName}/${sluggedFileName}`;\n const newFullPath = getPublicPath(newRelativePath);\n const newKey = \"/\" + newRelativePath;\n\n // Check if slugged name already exists\n if (!meta[newKey] && !existingKeys.has(newKey)) {\n try {\n await fs.mkdir(path.dirname(newFullPath), { recursive: true });\n await fs.rename(fullPath, newFullPath);\n renamed.push({ from: relativePath, to: newRelativePath });\n relativePath = newRelativePath;\n fullPath = newFullPath;\n imageKey = newKey;\n } catch (err) {\n console.error(`Failed to slugify ${relativePath}:`, err);\n // Continue with original name if rename fails\n }\n }\n }\n\n // Check for collision (path exists in meta but file is new)\n if (meta[imageKey]) {\n // Need to rename this file to avoid collision\n const ext = path.extname(relativePath);\n const baseName = relativePath.slice(0, -ext.length);\n let counter = 1;\n let newKey = `/${baseName}-${counter}${ext}`;\n\n while (meta[newKey]) {\n counter++;\n newKey = `/${baseName}-${counter}${ext}`;\n }\n\n // Rename the physical file\n const newRelativePath = `${baseName}-${counter}${ext}`;\n const newFullPath = getPublicPath(newRelativePath);\n\n try {\n await fs.rename(fullPath, newFullPath);\n renamed.push({ from: relativePath, to: newRelativePath });\n relativePath = newRelativePath;\n fullPath = newFullPath;\n imageKey = newKey;\n } catch (err) {\n console.error(`Failed to rename ${relativePath}:`, err);\n errors.push(`Failed to rename ${relativePath}`);\n continue;\n }\n }\n\n try {\n const isImage = isImageFile(relativePath);\n\n if (isImage) {\n // Read dimensions for images\n const ext = path.extname(relativePath).toLowerCase();\n\n if (ext === \".svg\") {\n // SVGs don't have pixel dimensions in the same way\n meta[imageKey] = { o: { w: 0, h: 0 } };\n } else {\n try {\n const buffer = await fs.readFile(fullPath);\n // Apply EXIF rotation to get correct dimensions\n const rotatedBuffer = await sharp(buffer).rotate().toBuffer();\n const metadata = await sharp(rotatedBuffer).metadata();\n\n meta[imageKey] = {\n o: { w: metadata.width || 0, h: metadata.height || 0 },\n };\n } catch {\n // Couldn't read dimensions\n meta[imageKey] = { o: { w: 0, h: 0 } };\n }\n }\n } else {\n // Non-image files - just add empty entry\n meta[imageKey] = {};\n }\n\n existingKeys.add(imageKey);\n added.push(imageKey);\n\n // Save meta periodically (every 10 files) to avoid losing progress on crash\n if (added.length % 10 === 0) {\n await saveMeta(meta);\n }\n } catch (error) {\n console.error(`Failed to process ${relativePath}:`, error);\n errors.push(relativePath);\n }\n }\n\n // Check for orphaned files in the images folder\n sendEvent({\n type: \"cleanup\",\n message: \"Checking for orphaned thumbnails...\",\n });\n\n // Build set of expected thumbnail paths from meta entries\n const expectedThumbnails = new Set<string>();\n const fileEntries = getFileEntries(meta);\n for (const [imageKey, entry] of fileEntries) {\n // Only track local thumbnails (not pushed to CDN)\n if (entry.c === undefined && isProcessed(entry)) {\n for (const thumbPath of getAllThumbnailPaths(imageKey)) {\n expectedThumbnails.add(thumbPath);\n }\n }\n }\n\n // Scan the images folder for orphaned files\n async function findOrphans(\n dir: string,\n relativePath: string = \"\"\n ): Promise<void> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.name.startsWith(\".\")) continue;\n\n const fullPath = path.join(dir, entry.name);\n const relPath = relativePath\n ? `${relativePath}/${entry.name}`\n : entry.name;\n\n if (entry.isDirectory()) {\n await findOrphans(fullPath, relPath);\n } else if (isImageFile(entry.name)) {\n const publicPath = `/images/${relPath}`;\n if (!expectedThumbnails.has(publicPath)) {\n orphanedFiles.push(publicPath);\n }\n }\n }\n } catch {\n // Directory might not exist\n }\n }\n\n const imagesDir = getPublicPath(\"images\");\n try {\n await findOrphans(imagesDir);\n } catch {\n // images dir might not exist\n }\n\n // Clean up empty folders in the public directory\n sendEvent({ type: \"cleanup\", message: \"Cleaning up empty folders...\" });\n let emptyFoldersDeleted = 0;\n\n async function cleanEmptyFolders(dir: string): Promise<void> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.name.startsWith(\".\")) continue;\n if (!entry.isDirectory()) continue;\n\n // Skip the images folder (handled separately by deleteOrphans)\n const fullPath = path.join(dir, entry.name);\n if (fullPath === imagesDir) continue;\n\n // Recursively clean subdirectories first\n await cleanEmptyFolders(fullPath);\n\n // Then try to delete this folder if empty\n try {\n const subEntries = await fs.readdir(fullPath);\n const meaningfulEntries = subEntries.filter(\n (e) => !e.startsWith(\".\")\n );\n if (meaningfulEntries.length === 0) {\n await fs.rm(fullPath, { recursive: true });\n emptyFoldersDeleted++;\n }\n } catch {\n // Folder might already be deleted or not accessible\n }\n }\n } catch {\n // Directory might not exist or not readable\n }\n }\n\n await cleanEmptyFolders(getPublicPath());\n\n // Also clean up empty folders inside the images directory\n async function cleanImagesEmptyFolders(dir: string): Promise<boolean> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n let isEmpty = true;\n\n for (const entry of entries) {\n if (entry.isDirectory()) {\n const subDirEmpty = await cleanImagesEmptyFolders(\n path.join(dir, entry.name)\n );\n if (!subDirEmpty) isEmpty = false;\n } else if (!entry.name.startsWith(\".\")) {\n isEmpty = false;\n }\n }\n\n if (isEmpty && dir !== imagesDir) {\n await fs.rm(dir, { recursive: true });\n emptyFoldersDeleted++;\n }\n\n return isEmpty;\n } catch {\n return true;\n }\n }\n\n try {\n await cleanImagesEmptyFolders(imagesDir);\n } catch {\n // images dir might not exist\n }\n\n // Clean up orphaned meta entries (local files that no longer exist)\n sendEvent({\n type: \"cleanup\",\n message: \"Checking for orphaned entries...\",\n });\n const orphanedEntries: string[] = [];\n const cdnUrls = (meta._cdns || []) as string[];\n const r2PublicUrl = (\n process.env.CLOUDFLARE_R2_PUBLIC_URL || \"\"\n ).replace(/\\/$/, \"\");\n\n for (const key of Object.keys(meta)) {\n if (key.startsWith(\"_\")) continue; // Skip special keys\n\n const entry = meta[key] as { c?: number; u?: 1 } | undefined;\n if (!entry) continue;\n\n // Skip cloud files - they still exist in the cloud\n if (entry.c !== undefined) {\n // But if it has u:1 flag, the local override might have been deleted\n if (entry.u === 1) {\n const localPath = getPublicPath(key);\n try {\n await fs.access(localPath);\n } catch {\n // Local override was deleted, remove the u flag\n delete entry.u;\n }\n }\n continue;\n }\n\n // For local files, check if they still exist\n const localPath = getPublicPath(key);\n try {\n await fs.access(localPath);\n } catch {\n // File doesn't exist - orphaned entry\n orphanedEntries.push(key);\n delete meta[key];\n }\n }\n\n if (orphanedEntries.length > 0) {\n sendEvent({\n type: \"cleanup\",\n message: `Removed ${orphanedEntries.length} orphaned entries...`,\n });\n }\n\n await saveMeta(meta);\n\n sendEvent({\n type: \"complete\",\n existingCount,\n added: added.length,\n renamed: renamed.length,\n errors: errors.length,\n renamedFiles: renamed,\n orphanedFiles: orphanedFiles.length > 0 ? orphanedFiles : undefined,\n pendingUpdates: pendingUpdates.length,\n orphanedEntries: orphanedEntries.length,\n emptyFoldersDeleted:\n emptyFoldersDeleted > 0 ? emptyFoldersDeleted : undefined,\n });\n } catch (error) {\n console.error(\"Scan failed:\", error);\n sendEvent({ type: \"error\", message: \"Scan failed\" });\n } finally {\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n}\n\n/**\n * Delete orphaned files from the images folder\n */\nexport async function handleDeleteOrphans(request: Request) {\n try {\n const { paths } = (await request.json()) as { paths: string[] };\n\n if (!paths || !Array.isArray(paths) || paths.length === 0) {\n return jsonResponse({ error: \"No paths provided\" }, { status: 400 });\n }\n\n const deleted: string[] = [];\n const errors: string[] = [];\n\n for (const orphanPath of paths) {\n // Ensure the path is within the images folder for safety\n if (!orphanPath.startsWith(\"/images/\")) {\n errors.push(`Invalid path: ${orphanPath}`);\n continue;\n }\n\n const fullPath = getPublicPath(orphanPath);\n\n try {\n await fs.unlink(fullPath);\n deleted.push(orphanPath);\n } catch (err) {\n console.error(`Failed to delete ${orphanPath}:`, err);\n errors.push(orphanPath);\n }\n }\n\n // Clean up empty directories (including images folder itself)\n const imagesDir = getPublicPath(\"images\");\n\n try {\n await cleanupEmptyFoldersRecursive(imagesDir);\n } catch {\n // images dir might not exist\n }\n\n return jsonResponse({\n success: true,\n deleted: deleted.length,\n errors: errors.length,\n });\n } catch (error) {\n console.error(\"Failed to delete orphans:\", error);\n return jsonResponse(\n { error: \"Failed to delete orphaned files\" },\n { status: 500 }\n );\n }\n}\n","import sharp from \"sharp\";\nimport {\n loadMeta,\n saveMeta,\n getOrAddCdnIndex,\n getMetaEntry,\n setMetaEntry,\n} from \"./utils\";\nimport { isOperationCancelled, clearCancelledOperation } from \"./images\";\nimport type { Dimensions } from \"../types\";\n\n/**\n * Parse an image URL into base URL and path\n */\nfunction parseImageUrl(url: string): { base: string; path: string } {\n const parsed = new URL(url);\n // Base is protocol + host\n const base = `${parsed.protocol}//${parsed.host}`;\n // Path is everything after\n const path = parsed.pathname;\n return { base, path };\n}\n\n/**\n * Fetch remote image and get dimensions\n */\nasync function processRemoteImage(\n url: string\n): Promise<{ o: Dimensions; b?: string }> {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`Failed to fetch: ${response.status}`);\n }\n\n const buffer = Buffer.from(await response.arrayBuffer());\n\n // Apply EXIF rotation to get correct dimensions\n const rotatedBuffer = await sharp(buffer).rotate().toBuffer();\n const metadata = await sharp(rotatedBuffer).metadata();\n\n return {\n o: { w: metadata.width || 0, h: metadata.height || 0 },\n // b: blur hash would be generated here if needed\n };\n}\n\n/**\n * Streaming endpoint to import images from URLs\n */\nexport async function handleImportUrls(request: Request) {\n const encoder = new TextEncoder();\n\n const stream = new ReadableStream({\n async start(controller) {\n const sendEvent = (data: object) => {\n try {\n controller.enqueue(\n encoder.encode(`data: ${JSON.stringify(data)}\\n\\n`)\n );\n } catch {\n // Controller may be closed\n }\n };\n\n try {\n const { urls, operationId } = (await request.json()) as {\n urls: string[];\n operationId?: string;\n };\n\n if (!urls || !Array.isArray(urls) || urls.length === 0) {\n sendEvent({ type: \"error\", message: \"No URLs provided\" });\n controller.close();\n return;\n }\n\n // Helper to check if operation was cancelled\n const isCancelled = () =>\n operationId ? isOperationCancelled(operationId) : false;\n\n const meta = await loadMeta();\n const added: string[] = [];\n const skipped: string[] = [];\n const errors: string[] = [];\n\n const total = urls.length;\n sendEvent({ type: \"start\", total });\n\n for (let i = 0; i < urls.length; i++) {\n // Check for cancellation before each URL\n if (isCancelled()) {\n await saveMeta(meta);\n if (operationId) clearCancelledOperation(operationId);\n sendEvent({\n type: \"complete\",\n added: added.length,\n skipped: skipped.length,\n errors: errors.length,\n message: `Stopped. Imported ${added.length} URL${\n added.length !== 1 ? \"s\" : \"\"\n }.`,\n cancelled: true,\n });\n controller.close();\n return;\n }\n\n const url = urls[i].trim();\n if (!url) continue;\n\n try {\n // Parse URL to get base and path\n const { base, path } = parseImageUrl(url);\n\n // Check if this path already exists in meta\n const existingEntry = getMetaEntry(meta, path);\n if (existingEntry) {\n skipped.push(path);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n imported: added.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: url,\n });\n continue;\n }\n\n // Get or add CDN URL to _cdns array\n const cdnIndex = getOrAddCdnIndex(meta, base);\n\n // Fetch and process the image\n const imageData = await processRemoteImage(url);\n\n // Add entry to meta\n // Note: No thumbnail dims since this is an external image, not processed locally\n setMetaEntry(meta, path, {\n o: imageData.o,\n b: imageData.b,\n c: cdnIndex,\n });\n\n // Save meta incrementally after each successful import\n await saveMeta(meta);\n\n added.push(path);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n imported: added.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: url,\n });\n } catch (error) {\n console.error(`Failed to import ${url}:`, error);\n errors.push(url);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n imported: added.length,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: url,\n });\n }\n }\n\n await saveMeta(meta);\n if (operationId) clearCancelledOperation(operationId);\n\n sendEvent({\n type: \"complete\",\n added: added.length,\n skipped: skipped.length,\n errors: errors.length,\n });\n } catch (error) {\n console.error(\"Import failed:\", error);\n sendEvent({ type: \"error\", message: \"Import failed\" });\n } finally {\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n}\n\n/**\n * Get CDN URLs for settings\n */\nexport async function handleGetCdns() {\n try {\n const meta = await loadMeta();\n const cdns = meta._cdns || [];\n\n return Response.json({ cdns });\n } catch (error) {\n console.error(\"Failed to get CDNs:\", error);\n return Response.json({ error: \"Failed to get CDNs\" }, { status: 500 });\n }\n}\n\n/**\n * Update CDN URLs from settings\n */\nexport async function handleUpdateCdns(request: Request) {\n try {\n const { cdns } = (await request.json()) as { cdns: string[] };\n\n if (!Array.isArray(cdns)) {\n return Response.json({ error: \"Invalid CDN array\" }, { status: 400 });\n }\n\n const meta = await loadMeta();\n\n // Normalize URLs (remove trailing slashes)\n meta._cdns = cdns.map((url) => url.replace(/\\/$/, \"\"));\n\n await saveMeta(meta);\n\n return Response.json({ success: true, cdns: meta._cdns });\n } catch (error) {\n console.error(\"Failed to update CDNs:\", error);\n return Response.json({ error: \"Failed to update CDNs\" }, { status: 500 });\n }\n}\n","import sharp from \"sharp\";\nimport path from \"path\";\nimport fs from \"fs/promises\";\nimport { jsonResponse } from \"./utils/response\";\nimport { getPublicPath, getSrcAppPath } from \"../config\";\n\n/**\n * Generate favicon variants from a source image (streaming)\n *\n * Takes a favicon.png or favicon.jpg and generates:\n * - favicon.ico (48x48) - Classic ICO format\n * - icon.png (32x32) - Standard favicon\n * - apple-icon.png (180x180) - Apple touch icon\n *\n * All outputs are saved to src/app/ for Next.js metadata\n */\n\nconst FAVICON_CONFIGS = [\n { name: \"favicon.ico\", size: 48 },\n { name: \"icon.png\", size: 32 },\n { name: \"apple-icon.png\", size: 180 },\n];\n\nexport async function handleGenerateFavicon(request: Request) {\n const encoder = new TextEncoder();\n\n let imagePath: string;\n try {\n const body = (await request.json()) as { imagePath: string };\n imagePath = body.imagePath;\n\n if (!imagePath) {\n return jsonResponse({ error: \"No image path provided\" }, { status: 400 });\n }\n } catch {\n return jsonResponse({ error: \"Invalid request body\" }, { status: 400 });\n }\n\n // Validate filename is favicon.png or favicon.jpg\n const fileName = path.basename(imagePath).toLowerCase();\n if (fileName !== \"favicon.png\" && fileName !== \"favicon.jpg\") {\n return jsonResponse(\n {\n error: \"Source file must be named favicon.png or favicon.jpg\",\n },\n { status: 400 }\n );\n }\n\n // Build full path to source file\n const sourcePath = getPublicPath(imagePath.replace(/^\\//, \"\"));\n\n // Check if source file exists\n try {\n await fs.access(sourcePath);\n } catch {\n return jsonResponse({ error: \"Source file not found\" }, { status: 404 });\n }\n\n // Verify the source is a valid image (apply EXIF rotation for accurate dimensions)\n let metadata;\n try {\n const rotatedBuffer = await sharp(sourcePath).rotate().toBuffer();\n metadata = await sharp(rotatedBuffer).metadata();\n } catch {\n return jsonResponse(\n { error: \"Source file is not a valid image\" },\n { status: 400 }\n );\n }\n\n // Output directory is src/app/\n const outputDir = getSrcAppPath();\n\n // Check output directory exists\n try {\n await fs.access(outputDir);\n } catch {\n return jsonResponse(\n {\n error: \"Output directory src/app/ not found\",\n },\n { status: 500 }\n );\n }\n\n const stream = new ReadableStream({\n async start(controller) {\n const sendEvent = (data: object) => {\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\\n\\n`));\n };\n\n try {\n const total = FAVICON_CONFIGS.length;\n const generated: string[] = [];\n const errors: string[] = [];\n\n sendEvent({\n type: \"start\",\n total,\n sourceSize: `${metadata.width}x${metadata.height}`,\n });\n\n for (let i = 0; i < FAVICON_CONFIGS.length; i++) {\n const config = FAVICON_CONFIGS[i];\n\n try {\n const outputPath = path.join(outputDir, config.name);\n\n await sharp(sourcePath)\n .rotate() // Apply EXIF rotation\n .resize(config.size, config.size, {\n fit: \"cover\",\n position: \"center\",\n })\n .png({ quality: 100 })\n .toFile(outputPath);\n\n generated.push(config.name);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n processed: generated.length,\n percent: Math.round(((i + 1) / total) * 100),\n message: `Generated ${config.name}`,\n });\n } catch (error) {\n console.error(`Failed to generate ${config.name}:`, error);\n errors.push(config.name);\n sendEvent({\n type: \"progress\",\n current: i + 1,\n total,\n processed: generated.length,\n percent: Math.round(((i + 1) / total) * 100),\n message: `Failed: ${config.name}`,\n });\n }\n }\n\n // Build completion message\n let message = `Generated ${generated.length} favicon${\n generated.length !== 1 ? \"s\" : \"\"\n } to src/app/.`;\n if (errors.length > 0) {\n message += ` ${errors.length} failed.`;\n }\n\n sendEvent({\n type: \"complete\",\n processed: generated.length,\n errors: errors.length,\n message,\n });\n\n controller.close();\n } catch (error) {\n console.error(\"Favicon generation error:\", error);\n sendEvent({ type: \"error\", message: \"Failed to generate favicons\" });\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n}\n","import puppeteer from \"puppeteer\";\nimport path from \"path\";\nimport fs from \"fs/promises\";\nimport sharp from \"sharp\";\nimport { config as loadEnv } from \"dotenv\";\nimport { jsonResponse } from \"./utils/response\";\nimport { getPublicPath, getWorkspacePath } from \"../config\";\nimport { loadMeta, saveMeta, setMetaEntry } from \"./utils/meta\";\n\n/**\n * Parse an env file and return key-value pairs\n */\nfunction parseEnvFile(content: string): Record<string, string> {\n const result: Record<string, string> = {};\n for (const line of content.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) continue;\n const eqIndex = trimmed.indexOf(\"=\");\n if (eqIndex > 0) {\n const key = trimmed.slice(0, eqIndex).trim();\n let value = trimmed.slice(eqIndex + 1).trim();\n // Remove quotes if present\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1);\n }\n result[key] = value;\n }\n }\n return result;\n}\n\n/**\n * Generate featured image by screenshotting the homepage (streaming)\n *\n * Takes a screenshot of the project homepage and saves it as {project-name}.jpg\n * in the public folder. Useful for social previews, README, etc.\n *\n * The project name comes from package.json \"name\" field.\n * The homepage URL can be:\n * - Provided in the request body\n * - Read from package.json \"homepage\" field\n * - Falls back to STUDIO_DEV_SITE_URL env var\n * - Falls back to http://localhost:3000\n */\n\nexport async function handleGenerateFeaturedImage(request: Request) {\n const encoder = new TextEncoder();\n\n // Parse optional URL from request body\n let customUrl: string | undefined;\n try {\n const body = (await request.json()) as { url?: string };\n customUrl = body.url;\n } catch {\n // No body or invalid JSON is fine\n }\n\n const stream = new ReadableStream({\n async start(controller) {\n const sendEvent = (data: object) => {\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\\n\\n`));\n };\n\n try {\n // Read package.json to get homepage URL\n const packageJsonPath = getWorkspacePath(\"package.json\");\n let homepageUrl =\n customUrl ||\n process.env.STUDIO_DEV_SITE_URL ||\n \"http://localhost:3000\";\n\n try {\n const packageJsonContent = await fs.readFile(packageJsonPath, \"utf8\");\n const packageJson = JSON.parse(packageJsonContent);\n if (!customUrl && packageJson.homepage) {\n homepageUrl = packageJson.homepage;\n }\n } catch {\n // package.json not found or invalid, use defaults\n }\n\n const outputPath = getPublicPath(`screenshot.jpg`);\n const relativePath = `public/screenshot.jpg`;\n\n sendEvent({\n type: \"start\",\n total: 4,\n url: homepageUrl,\n output: relativePath,\n });\n\n // Step 1: Launch browser\n sendEvent({\n type: \"progress\",\n current: 1,\n total: 4,\n percent: 25,\n message: \"Launching browser...\",\n });\n\n const browser = await puppeteer.launch({\n headless: true,\n args: [\"--no-sandbox\", \"--disable-setuid-sandbox\"],\n });\n\n try {\n // Step 2: Navigate to page\n sendEvent({\n type: \"progress\",\n current: 2,\n total: 4,\n percent: 50,\n message: `Navigating to ${homepageUrl}...`,\n });\n\n const page = await browser.newPage();\n\n // Set viewport to 2000x1000 (2:1 aspect ratio for social previews)\n await page.setViewport({\n width: 2000,\n height: 1000,\n deviceScaleFactor: 2, // Retina display quality\n });\n\n await page.goto(homepageUrl, {\n waitUntil: \"networkidle2\",\n timeout: 30000,\n });\n\n // Wait for animations/fonts to load\n await new Promise((resolve) => setTimeout(resolve, 2000));\n\n // Step 3: Take screenshot\n sendEvent({\n type: \"progress\",\n current: 3,\n total: 4,\n percent: 75,\n message: \"Taking screenshot...\",\n });\n\n await page.screenshot({\n path: outputPath,\n type: \"jpeg\",\n quality: 90,\n });\n\n // Step 4: Add to _studio.json\n sendEvent({\n type: \"progress\",\n current: 4,\n total: 4,\n percent: 95,\n message: \"Updating metadata...\",\n });\n\n // Get image dimensions using sharp\n const imageBuffer = await fs.readFile(outputPath);\n const metadata = await sharp(imageBuffer).metadata();\n const width = metadata.width || 0;\n const height = metadata.height || 0;\n\n // Add to _studio.json\n // Only set 'o' (original dimensions), not 'f' (full thumbnail)\n // because the featured image is stored at root level, not in /images/\n const meta = await loadMeta();\n const metaKey = `/screenshot.jpg`;\n setMetaEntry(meta, metaKey, {\n o: { w: width, h: height },\n });\n await saveMeta(meta);\n\n sendEvent({\n type: \"complete\",\n processed: 1,\n errors: 0,\n outputPath: relativePath,\n message: `Screenshot saved to ${relativePath}`,\n });\n } finally {\n await browser.close();\n }\n\n controller.close();\n } catch (error) {\n console.error(\"Featured image generation error:\", error);\n const errorMessage =\n error instanceof Error ? error.message : \"Unknown error\";\n sendEvent({\n type: \"error\",\n message: `Failed to generate screenshot: ${errorMessage}`,\n });\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n}\n\n/**\n * Get URL options for featured image generation\n * Returns the dev URL from .env.local and production URL from .env.production\n */\nexport async function handleGetFeaturedImageOptions() {\n try {\n const packageJsonPath = getWorkspacePath(\"package.json\");\n const envLocalPath = getWorkspacePath(\".env.local\");\n const envProductionPath = getWorkspacePath(\".env.production\");\n\n let projectName = \"featured-image\";\n let devUrl: string | null = null;\n let productionUrl: string | null = null;\n\n // Read project name from package.json\n try {\n const packageJsonContent = await fs.readFile(packageJsonPath, \"utf8\");\n const packageJson = JSON.parse(packageJsonContent);\n projectName = packageJson.name || \"featured-image\";\n } catch {\n // package.json not found or invalid\n }\n\n // Read dev URL from .env.local\n try {\n const envLocalContent = await fs.readFile(envLocalPath, \"utf8\");\n const envLocal = parseEnvFile(envLocalContent);\n devUrl = envLocal.NEXT_PUBLIC_PRODUCTION_URL || null;\n } catch {\n // .env.local not found\n }\n\n // Read production URL from .env.production\n try {\n const envProductionContent = await fs.readFile(envProductionPath, \"utf8\");\n const envProduction = parseEnvFile(envProductionContent);\n productionUrl = envProduction.NEXT_PUBLIC_PRODUCTION_URL || null;\n } catch {\n // .env.production not found\n }\n\n return jsonResponse({\n projectName,\n devUrl,\n productionUrl,\n });\n } catch (error) {\n console.error(\"Get featured image options error:\", error);\n return jsonResponse(\n { error: \"Failed to get featured image options\" },\n { status: 500 }\n );\n }\n}\n\n/**\n * Check if the featured image exists in _studio.json\n * Returns the expected filename and whether it exists\n */\nexport async function handleCheckFeaturedImage() {\n try {\n const expectedFilename = `screenshot.jpg`;\n const metaKey = `/screenshot.jpg`;\n\n // Check if the image exists in _studio.json\n const meta = await loadMeta();\n const exists = metaKey in meta && !Array.isArray(meta[metaKey]);\n\n return jsonResponse({\n filename: expectedFilename,\n exists,\n });\n } catch (error) {\n console.error(\"Check featured image error:\", error);\n return jsonResponse(\n { error: \"Failed to check featured image\" },\n { status: 500 }\n );\n }\n}\n","import { promises as fs } from \"fs\";\nimport path from \"path\";\nimport sharp from \"sharp\";\nimport type { FileItem, MetaEntry } from \"../types\";\nimport { getAllThumbnailPaths } from \"../types\";\nimport { loadMeta, saveMeta } from \"./utils/meta\";\nimport { getPublicPath, getWorkspacePath } from \"../config\";\nimport { jsonResponse } from \"./utils/response\";\nimport { isImageFile } from \"./utils/files\";\n\ninterface EditImageRequest {\n imagePath: string;\n crop: {\n x: number;\n y: number;\n width: number;\n height: number;\n };\n rotation: number; // 0, 90, 180, 270\n resize: {\n width: number;\n height: number;\n };\n quality?: number; // 1-100, defaults to 95\n}\n\n/**\n * Edit an image: crop, rotate, and resize\n * \n * Order of operations:\n * 1. Apply EXIF rotation to normalize (matches browser display)\n * 2. Extract the cropped region (crop coords are from client's view)\n * 3. Rotate the cropped result\n * 4. Resize to final dimensions\n * 5. Save and update metadata\n */\nexport async function handleEditImage(request: Request) {\n try {\n const body = (await request.json()) as EditImageRequest;\n const { imagePath, crop, rotation, resize, quality = 95 } = body;\n \n // Clamp quality to valid range\n const outputQuality = Math.max(1, Math.min(100, quality));\n\n // Validate input\n if (!imagePath || !imagePath.startsWith(\"public/\")) {\n return jsonResponse({ error: \"Invalid image path\" }, { status: 400 });\n }\n\n if (!isImageFile(path.basename(imagePath))) {\n return jsonResponse({ error: \"Not an image file\" }, { status: 400 });\n }\n\n // Check that the file exists locally\n const absolutePath = getWorkspacePath(imagePath);\n try {\n await fs.access(absolutePath);\n } catch {\n return jsonResponse(\n { error: \"Image file not found locally. Download it first.\" },\n { status: 404 }\n );\n }\n\n // Read the original image\n const imageBuffer = await fs.readFile(absolutePath);\n \n // Step 1: Apply EXIF rotation first to normalize the image\n // This matches what the browser displays\n const exifCorrectedBuffer = await sharp(imageBuffer).rotate().toBuffer();\n const exifMeta = await sharp(exifCorrectedBuffer).metadata();\n const exifWidth = exifMeta.width || 0;\n const exifHeight = exifMeta.height || 0;\n\n // Step 2: Apply crop FIRST (extract region)\n // Crop coordinates are from the client's view (EXIF-corrected, non-rotated)\n const cropX = Math.max(0, Math.min(crop.x, exifWidth - 1));\n const cropY = Math.max(0, Math.min(crop.y, exifHeight - 1));\n const cropWidth = Math.min(crop.width, exifWidth - cropX);\n const cropHeight = Math.min(crop.height, exifHeight - cropY);\n\n let pipeline = sharp(exifCorrectedBuffer);\n \n // Only extract if crop is different from full image\n if (cropX > 0 || cropY > 0 || cropWidth < exifWidth || cropHeight < exifHeight) {\n pipeline = pipeline.extract({\n left: Math.round(cropX),\n top: Math.round(cropY),\n width: Math.round(cropWidth),\n height: Math.round(cropHeight),\n });\n }\n\n // Step 3: Apply user rotation if any (90° increments) AFTER cropping\n if (rotation !== 0) {\n pipeline = pipeline.rotate(rotation);\n }\n\n // Step 4: Apply resize (if different from expected output)\n // Note: After rotation, dimensions may have swapped\n pipeline = pipeline.resize(resize.width, resize.height);\n\n // Determine output format based on original file extension\n const ext = path.extname(imagePath).toLowerCase();\n let finalBuffer: Buffer;\n \n if (ext === \".png\") {\n // PNG is lossless, compressionLevel doesn't affect quality\n finalBuffer = await pipeline.png({ compressionLevel: 9 }).toBuffer();\n } else if (ext === \".webp\") {\n finalBuffer = await pipeline.webp({ quality: outputQuality, lossless: outputQuality === 100 }).toBuffer();\n } else if (ext === \".gif\") {\n finalBuffer = await pipeline.gif().toBuffer();\n } else {\n // Default to JPEG for jpg/jpeg\n finalBuffer = await pipeline.jpeg({ quality: outputQuality, mozjpeg: true }).toBuffer();\n }\n\n // Get final dimensions\n const finalMeta = await sharp(finalBuffer).metadata();\n const finalWidth = finalMeta.width || resize.width;\n const finalHeight = finalMeta.height || resize.height;\n\n // 4. Save the edited image (overwrite original)\n await fs.writeFile(absolutePath, finalBuffer);\n\n // 5. Update metadata\n const meta = await loadMeta();\n const imageKey = \"/\" + imagePath.replace(/^public\\//, \"\");\n const entry = meta[imageKey] as MetaEntry | undefined;\n\n // Update dimensions\n const updatedEntry: MetaEntry = {\n ...entry,\n o: { w: finalWidth, h: finalHeight },\n };\n\n // Clear thumbnail dimensions (they need regeneration)\n delete updatedEntry.sm;\n delete updatedEntry.md;\n delete updatedEntry.lg;\n delete updatedEntry.f;\n\n meta[imageKey] = updatedEntry;\n await saveMeta(meta);\n\n // 6. Delete old thumbnails\n const thumbnailPaths = getAllThumbnailPaths(imageKey);\n for (const thumbPath of thumbnailPaths) {\n const absoluteThumbPath = getPublicPath(thumbPath);\n try {\n await fs.unlink(absoluteThumbPath);\n } catch {\n // Thumbnail might not exist\n }\n }\n\n // 7. Build updated FileItem for response\n const stats = await fs.stat(absolutePath);\n const updatedItem: Partial<FileItem> = {\n name: path.basename(imagePath),\n path: imagePath,\n type: \"file\",\n size: stats.size,\n dimensions: { width: finalWidth, height: finalHeight },\n hasSm: false,\n hasMd: false,\n hasLg: false,\n hasFull: false,\n hasThumbnail: false,\n // Preserve CDN status from original entry\n cdnPushed: entry?.c !== undefined,\n isRemote: false, // If we're editing, it's local\n hasUpdate: entry?.c !== undefined, // If was on CDN, now has local update\n };\n\n return jsonResponse({\n success: true,\n updatedItem,\n dimensions: { width: finalWidth, height: finalHeight },\n });\n } catch (error) {\n console.error(\"Edit image error:\", error);\n const message = error instanceof Error ? error.message : \"Unknown error\";\n return jsonResponse(\n { error: `Failed to edit image: ${message}` },\n { status: 500 }\n );\n }\n}\n","import { promises as fs } from 'fs'\nimport path from 'path'\nimport { getWorkspacePath } from '../config'\nimport { jsonResponse } from './utils/response'\nimport type { FileItem } from '../types'\n\n/**\n * List files and folders for fonts paths\n * Works like the regular list handler but only for _fonts/\n */\nexport async function handleFontsList(request: Request): Promise<Response> {\n const searchParams = new URL(request.url).searchParams\n const requestedPath = searchParams.get('path') || '_fonts'\n\n try {\n const items: FileItem[] = []\n\n // Only allow paths within _fonts/\n const isAllowed = requestedPath === '_fonts' || requestedPath.startsWith('_fonts/')\n\n if (!isAllowed) {\n return jsonResponse({ items: [], error: 'Path not allowed' }, { status: 400 })\n }\n\n const fsPath = getWorkspacePath(requestedPath)\n\n // Check if directory exists\n try {\n const stat = await fs.stat(fsPath)\n if (!stat.isDirectory()) {\n return jsonResponse({ items: [] })\n }\n } catch {\n // Directory doesn't exist\n return jsonResponse({ items: [], canCreate: true })\n }\n\n // Read directory contents\n const entries = await fs.readdir(fsPath, { withFileTypes: true })\n\n for (const entry of entries) {\n const itemPath = `${requestedPath}/${entry.name}`\n\n if (entry.isDirectory()) {\n // Count files in folder\n let fileCount = 0\n try {\n const subEntries = await fs.readdir(path.join(fsPath, entry.name))\n fileCount = subEntries.filter(f => \n f.match(/\\.(ttf|woff2?|otf|ts|tsx|js)$/i)\n ).length\n } catch {\n // Ignore errors counting\n }\n\n items.push({\n name: entry.name,\n path: itemPath,\n type: 'folder',\n fileCount,\n })\n } else {\n // Only show font files and ts/js files\n const ext = path.extname(entry.name).toLowerCase()\n const allowedExts = ['.ttf', '.woff', '.woff2', '.otf', '.ts', '.tsx', '.js']\n\n if (allowedExts.includes(ext)) {\n // Get file size\n let size = 0\n try {\n const fileStat = await fs.stat(path.join(fsPath, entry.name))\n size = fileStat.size\n } catch {\n // Ignore\n }\n\n items.push({\n name: entry.name,\n path: itemPath,\n type: 'file',\n size,\n })\n }\n }\n }\n\n // Sort: folders first, then alphabetically\n items.sort((a, b) => {\n if (a.type === 'folder' && b.type !== 'folder') return -1\n if (a.type !== 'folder' && b.type === 'folder') return 1\n return a.name.localeCompare(b.name)\n })\n\n return jsonResponse({ items })\n } catch (error) {\n console.error('Error listing fonts:', error)\n return jsonResponse({ error: 'Failed to list fonts' }, { status: 500 })\n }\n}\n\n/**\n * Upload TTF font files\n */\nexport async function handleFontsUpload(request: Request): Promise<Response> {\n try {\n const formData = await request.formData()\n const file = formData.get('file') as File | null\n const targetPath = formData.get('path') as string || '_fonts'\n\n if (!file) {\n return jsonResponse({ error: 'No file provided' }, { status: 400 })\n }\n\n // Only accept TTF files\n if (!file.name.toLowerCase().endsWith('.ttf')) {\n return jsonResponse({ error: 'Only TTF files are supported' }, { status: 400 })\n }\n\n // Validate path\n if (!targetPath.startsWith('_fonts')) {\n return jsonResponse({ error: 'Can only upload to _fonts/' }, { status: 400 })\n }\n\n const bytes = await file.arrayBuffer()\n const buffer = Buffer.from(bytes)\n\n // Ensure directory exists\n const uploadDir = getWorkspacePath(targetPath)\n await fs.mkdir(uploadDir, { recursive: true })\n\n // Save file\n const filePath = path.join(uploadDir, file.name.toLowerCase())\n await fs.writeFile(filePath, buffer)\n\n return jsonResponse({\n success: true,\n path: `${targetPath}/${file.name.toLowerCase()}`,\n })\n } catch (error) {\n console.error('Error uploading font:', error)\n return jsonResponse({ error: 'Failed to upload font' }, { status: 500 })\n }\n}\n\n/**\n * Create a folder in _fonts\n */\nexport async function handleFontsCreateFolder(request: Request): Promise<Response> {\n try {\n const { path: targetPath, name } = await request.json()\n\n if (!targetPath || !name) {\n return jsonResponse({ error: 'Path and name are required' }, { status: 400 })\n }\n\n // Only allow paths within _fonts/\n const isAllowed = targetPath === '_fonts' || targetPath.startsWith('_fonts/')\n\n if (!isAllowed) {\n return jsonResponse({ error: 'Path not allowed' }, { status: 400 })\n }\n\n const folderPath = getWorkspacePath(targetPath, name.toLowerCase())\n await fs.mkdir(folderPath, { recursive: true })\n\n return jsonResponse({\n success: true,\n path: `${targetPath}/${name.toLowerCase()}`,\n })\n } catch (error) {\n console.error('Error creating folder:', error)\n return jsonResponse({ error: 'Failed to create folder' }, { status: 500 })\n }\n}\n\n/**\n * Delete files or folders from _fonts\n */\nexport async function handleFontsDelete(request: Request): Promise<Response> {\n try {\n const { paths } = await request.json()\n\n if (!paths || !Array.isArray(paths) || paths.length === 0) {\n return jsonResponse({ error: 'Paths are required' }, { status: 400 })\n }\n\n // Validate all paths - only allow within _fonts/\n for (const p of paths) {\n if (!p.startsWith('_fonts/')) {\n return jsonResponse({ error: `Path not allowed: ${p}` }, { status: 400 })\n }\n }\n\n const deleted: string[] = []\n const errors: string[] = []\n\n for (const p of paths) {\n try {\n const fullPath = getWorkspacePath(p)\n const stat = await fs.stat(fullPath)\n\n if (stat.isDirectory()) {\n await fs.rm(fullPath, { recursive: true })\n } else {\n await fs.unlink(fullPath)\n }\n deleted.push(p)\n } catch (err) {\n errors.push(`Failed to delete ${p}: ${err instanceof Error ? err.message : 'Unknown error'}`)\n }\n }\n\n return jsonResponse({\n success: true,\n deleted,\n errors: errors.length > 0 ? errors : undefined,\n })\n } catch (error) {\n console.error('Error deleting:', error)\n return jsonResponse({ error: 'Failed to delete' }, { status: 500 })\n }\n}\n\n/**\n * Rename a folder in _fonts\n * Also renames all files inside to match the new folder name convention\n */\nexport async function handleFontsRename(request: Request): Promise<Response> {\n try {\n const { oldPath, newName } = await request.json()\n\n if (!oldPath || !newName) {\n return jsonResponse({ error: 'oldPath and newName are required' }, { status: 400 })\n }\n\n // Only allow renaming within _fonts/\n if (!oldPath.startsWith('_fonts/')) {\n return jsonResponse({ error: 'Can only rename items in _fonts/' }, { status: 400 })\n }\n\n // Validate newName (no slashes, etc.)\n if (newName.includes('/') || newName.includes('\\\\')) {\n return jsonResponse({ error: 'Invalid folder name' }, { status: 400 })\n }\n\n const oldFullPath = getWorkspacePath(oldPath)\n \n // Get the parent directory and construct new path\n const parentDir = path.dirname(oldPath)\n const oldFolderName = path.basename(oldPath).toLowerCase()\n const newFolderName = newName.toLowerCase()\n const newPath = `${parentDir}/${newFolderName}`\n const newFullPath = getWorkspacePath(newPath)\n\n // Check if old path exists and is a directory\n let isDirectory = false\n try {\n const stat = await fs.stat(oldFullPath)\n isDirectory = stat.isDirectory()\n } catch {\n return jsonResponse({ error: 'Path not found' }, { status: 404 })\n }\n\n // Check if new path already exists\n try {\n await fs.stat(newFullPath)\n return jsonResponse({ error: 'A folder with that name already exists' }, { status: 400 })\n } catch {\n // Good, it doesn't exist\n }\n\n // Rename the folder first\n await fs.rename(oldFullPath, newFullPath)\n\n // If it's a directory, also rename files inside to match the new folder name\n if (isDirectory) {\n try {\n const entries = await fs.readdir(newFullPath)\n \n for (const entry of entries) {\n const entryLower = entry.toLowerCase()\n \n // Check if file starts with old folder name followed by - or _\n if (entryLower.startsWith(oldFolderName + '-') || entryLower.startsWith(oldFolderName + '_')) {\n // Determine the separator used\n const separator = entryLower.startsWith(oldFolderName + '-') ? '-' : '_'\n const suffix = entry.substring(oldFolderName.length) // includes the separator and everything after\n const newFileName = newFolderName + suffix.toLowerCase()\n \n const oldFilePath = path.join(newFullPath, entry)\n const newFilePath = path.join(newFullPath, newFileName)\n \n await fs.rename(oldFilePath, newFilePath)\n }\n }\n } catch (err) {\n console.error('Error renaming files inside folder:', err)\n // Don't fail the whole operation, folder was already renamed\n }\n }\n\n return jsonResponse({\n success: true,\n oldPath,\n newPath,\n })\n } catch (error) {\n console.error('Error renaming:', error)\n return jsonResponse({ error: 'Failed to rename' }, { status: 500 })\n }\n}\n"],"mappings":"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","names":["fs","path","path","fs","path","path","fs","fs","ext","base","outputExt","resolve","fs","fs","path","fs","path","sharp","fs","path","path","fs","fs","path","fs","path","S3Client","PutObjectCommand","DeleteObjectCommand","S3Client","fs","PutObjectCommand","path","fs","path","fs","path","S3Client","DeleteObjectCommand","PutObjectCommand","path","fs","sharp","newPath","meta","oldKey","entry","newKey","oldThumbFolder","isInCloud","fileCdnUrl","isInOurR2","hasThumbnails","fs","path","sharp","fs","path","sharp","localPath","sharp","path","sharp","sharp","path","fs","path","fs","sharp","fs","sharp","packageJsonPath","fs","packageJson","resolve","sharp","fs","path","sharp","path","fs","sharp","fs","path","fs","path","resolve"]}