@gallop.software/studio 2.2.15 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -15
- package/dist/client/assets/index-CidaYZsl.js +79 -0
- package/dist/client/index.html +1 -1
- package/dist/server/index.js +11 -138
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
package/dist/server/index.js.map
CHANGED
|
@@ -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"],"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 { handleList, handleSearch, handleListFolders, handleCountImages, handleFolderImages } from '../handlers/list'\nimport { handleUpload, handleDelete, handleCreateFolder, handleRename, handleMoveStream } from '../handlers/files'\nimport { handleSync, handleReprocessStream, handleUnprocessStream, handleDownloadStream, handlePushUpdatesStream, handleCancelUpdates, handleClearCache } from '../handlers/images'\nimport { handleScanStream, handleDeleteOrphans } from '../handlers/scan'\nimport { handleImportUrls, handleGetCdns, handleUpdateCdns } from '../handlers/import'\nimport { handleGenerateFavicon } from '../handlers/favicon'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = dirname(__filename)\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(startPort: number, maxAttempts = 10): 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(`No available port found between ${startPort} and ${startPort + maxAttempts - 1}`)\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.studio only\n const envStudioPath = join(workspace, '.env.studio')\n \n if (existsSync(envStudioPath)) {\n loadEnv({ path: envStudioPath, quiet: true })\n }\n\n // Middleware - skip JSON parsing for upload route (needs raw body for FormData)\n app.use((req, res, next) => {\n if (req.path === '/api/studio/upload') {\n next()\n } else {\n express.json({ limit: '50mb' })(req, res, next)\n }\n })\n app.use((req, res, next) => {\n if (req.path === '/api/studio/upload') {\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\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/move', wrapHandler(handleMoveStream, true))\n app.post('/api/studio/sync', wrapHandler(handleSync, true))\n app.post('/api/studio/reprocess-stream', wrapHandler(handleReprocessStream, true))\n app.post('/api/studio/unprocess-stream', wrapHandler(handleUnprocessStream, true))\n app.post('/api/studio/download-stream', wrapHandler(handleDownloadStream, true))\n app.post('/api/studio/push-updates-stream', wrapHandler(handlePushUpdatesStream, true))\n app.post('/api/studio/cancel-updates', wrapHandler(handleCancelUpdates))\n app.post('/api/studio/clear-cache', wrapHandler(handleClearCache))\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('/api/studio/generate-favicon', wrapHandler(handleGenerateFavicon, true))\n\n // API Routes - DELETE endpoints\n app.post('/api/studio/delete', wrapHandler(handleDelete))\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 app.listen(port, () => {\n console.log(`\n┌─────────────────────────────────────┐\n│ Studio - Media Manager │\n├─────────────────────────────────────┤\n│ Workspace: ${workspace.length > 24 ? '...' + workspace.slice(-21) : workspace.padEnd(24)}│\n│ URL: http://localhost:${port} │\n└─────────────────────────────────────┘\n`)\n\n if (open) {\n import('open').then((mod) => {\n mod.default(`http://localhost:${port}`)\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(req: Request): 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(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 // 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(originalPath: string, entry: MetaEntry): Array<{ path: string; size: 'f' | 'lg' | 'md' | 'sm' }> {\n const thumbnails: Array<{ path: string; size: 'f' | 'lg' | 'md' | 'sm' }> = []\n \n if (entry.f) {\n thumbnails.push({ path: getThumbnailPath(originalPath, 'full'), size: 'f' })\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): { cloudCount: number; remoteCount: number; localCount: number; updateCount: number } {\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 = 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 = 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<{ path: string; size: 'f' | 'lg' | 'md' | 'sm'; originalKey: string }> = []\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(([k]) => k === thumb.originalKey)?.[1]\n const cdnIndex = originalEntry?.c\n const cdnBaseUrl = cdnIndex !== undefined ? cdnUrls[cdnIndex] : undefined\n // Build the full thumbnail URL (with CDN base if applicable)\n const thumbnailUrl = cdnBaseUrl ? `${cdnBaseUrl}${thumb.path}` : 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 = isPushedToCloud && normalizedCdnBaseUrl !== r2PublicUrl\n \n // Get dimensions for this thumbnail size\n const thumbDims = originalEntry?.[thumb.size]\n const dimensions = thumbDims ? { width: thumbDims.w, height: thumbDims.h } : 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 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 (!thumbRelative.startsWith(imagesSubPath + '/') && thumbRelative !== imagesSubPath) 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 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 ? `public/${relativePath}/${entry.name}` : `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(key, metaEntry).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 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 = pathPrefix === '/' ? `/${entry.name}/` : `${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(folderPrefix, fileEntries, cdnUrls, r2PublicUrl)\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 = 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 = pathPrefix === '/' ? `/${folderName}/` : `${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(folderPrefix, fileEntries, cdnUrls, r2PublicUrl)\n \n items.push({\n name: folderName,\n path: relativePath ? `public/${relativePath}/${folderName}` : `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 = isPushedToCloud && entry.c !== undefined ? cdnUrls[entry.c] : undefined\n const normalizedFileCdnUrl = fileCdnUrl?.replace(/\\/$/, '') || ''\n const isRemote = isPushedToCloud && (!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 - use thumbnail\n const thumbPath = getThumbnailPath(key, 'sm')\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\n thumbnail = key\n hasThumbnail = false\n }\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 ? `public/${relativePath}/${fileName}` : `public/${fileName}`,\n type: 'file',\n size: fileSize,\n thumbnail,\n hasThumbnail,\n isProcessed: entryIsProcessed,\n cdnPushed: isPushedToCloud,\n cdnBaseUrl: fileCdnUrl,\n isRemote,\n isProtected: isInsideImagesFolder,\n dimensions: entry.o ? { width: entry.o.w, height: entry.o.h } : 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 \n if (query.length < 2) {\n return jsonResponse({ items: [] })\n }\n\n try {\n const meta = await loadMeta()\n const fileEntries = getFileEntries(meta)\n const cdnUrls = getCdnUrls(meta)\n const r2PublicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/$/, '') || ''\n const items: FileItem[] = []\n\n for (const [key, entry] of fileEntries) {\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 = isPushedToCloud && entry.c !== undefined ? cdnUrls[entry.c] : undefined\n const normalizedFileCdnUrl = fileCdnUrl?.replace(/\\/$/, '') || ''\n const isRemote = isPushedToCloud && (!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 - use thumbnail\n const thumbPath = getThumbnailPath(key, 'sm')\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 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 cdnPushed: isPushedToCloud,\n cdnBaseUrl: fileCdnUrl,\n isRemote,\n dimensions: entry.o ? { width: entry.o.w, height: entry.o.h } : 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 (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'images') {\n const folderRelPath = relativePath ? `${relativePath}/${entry.name}` : 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({ error: 'Failed to get folder files' }, { status: 500 })\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\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 { encode } from 'blurhash'\nimport type { MetaEntry, Dimensions } from '../../types'\nimport { getPublicPath } from '../../config'\n\nexport const FULL_MAX_WIDTH = 2560\n\nexport const DEFAULT_SIZES: Record<string, { width: number; suffix: string; key: 'sm' | 'md' | 'lg' }> = {\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 const sharpInstance = sharp(buffer)\n const metadata = await sharpInstance.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('/') ? imageKey.slice(1) : imageKey\n const baseName = path.basename(keyWithoutSlash, path.extname(keyWithoutSlash))\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 = imageDir === '.' ? `${baseName}${outputExt}` : `${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(buffer).resize(fullWidth, fullHeight).png({ quality: 85 }).toFile(fullPath)\n } else {\n await sharp(buffer).resize(fullWidth, fullHeight).jpeg({ quality: 85 }).toFile(fullPath)\n }\n } else {\n if (isPng) {\n await sharp(buffer).png({ quality: 85 }).toFile(fullPath)\n } else {\n await sharp(buffer).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 = imageDir === '.' ? sizeFileName : `${imageDir}/${sizeFileName}`\n const sizePath = getPublicPath('images', sizeFilePath)\n\n if (isPng) {\n await sharp(buffer).resize(maxWidth, newHeight).png({ quality: 80 }).toFile(sizePath)\n } else {\n await sharp(buffer).resize(maxWidth, newHeight).jpeg({ quality: 80 }).toFile(sizePath)\n }\n\n entry[key] = { w: maxWidth, h: newHeight }\n }\n\n // Generate blurhash\n const { data, info } = await sharp(buffer)\n .resize(32, 32, { fit: 'inside' })\n .ensureAlpha()\n .raw()\n .toBuffer({ resolveWithObject: true })\n\n entry.b = encode(new Uint8ClampedArray(data), info.width, info.height, 4, 4)\n\n return entry\n}\n","import { promises as fs } from 'fs'\nimport { S3Client, GetObjectCommand, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3'\nimport { getAllThumbnailPaths } from '../../types'\nimport { getContentType } from './files'\nimport { getPublicPath } from '../../config'\n\nexport type CachePurgeResult = {\n status: 'success' | 'not_configured' | 'failed'\n message?: string\n}\n\n/**\n * Purge URLs from Cloudflare cache\n * Requires CLOUDFLARE_ZONE_ID and CLOUDFLARE_API_TOKEN environment variables\n */\nexport async function purgeCloudflareCache(urls: string[]): Promise<CachePurgeResult> {\n const zoneId = process.env.CLOUDFLARE_ZONE_ID\n const apiToken = process.env.CLOUDFLARE_API_TOKEN\n \n if (urls.length === 0) {\n return { status: 'success' }\n }\n \n if (!zoneId || !apiToken) {\n return { \n status: 'not_configured',\n message: 'Cache purge skipped. To enable, add CLOUDFLARE_ZONE_ID and CLOUDFLARE_API_TOKEN to .env.studio'\n }\n }\n \n try {\n const response = await fetch(\n `https://api.cloudflare.com/client/v4/zones/${zoneId}/purge_cache`,\n {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${apiToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ files: urls }),\n }\n )\n \n if (!response.ok) {\n const text = await response.text()\n console.error('Cache purge failed:', text)\n return {\n status: 'failed',\n message: 'Cache purge failed. Check CLOUDFLARE_ZONE_ID and CLOUDFLARE_API_TOKEN in .env.studio'\n }\n }\n \n return { status: 'success', message: 'Cache cleared successfully.' }\n } catch (error) {\n console.error('Cache purge error:', error)\n return {\n status: 'failed',\n message: 'Cache purge failed. Check your network connection.'\n }\n }\n}\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 * 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, b: blurhash, 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 b?: string // blurhash\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}\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}, b: \"...\", 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 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 // 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(originalPath: string, size: 'sm' | 'md' | 'lg' | 'full'): string {\n if (size === 'full') {\n const ext = originalPath.match(/\\.\\w+$/)?.[0] || '.jpg'\n const base = originalPath.replace(/\\.\\w+$/, '')\n const outputExt = ext.toLowerCase() === '.png' ? '.png' : '.jpg'\n return `/images${base}${outputExt}`\n }\n const ext = originalPath.match(/\\.\\w+$/)?.[0] || '.jpg'\n const base = originalPath.replace(/\\.\\w+$/, '')\n const outputExt = ext.toLowerCase() === '.png' ? '.png' : '.jpg'\n return `/images${base}-${size}${outputExt}`\n}\n\n/**\n * Get all thumbnail paths for an image\n */\nexport function getAllThumbnailPaths(originalPath: string): string[] {\n return [\n getThumbnailPath(originalPath, 'full'),\n getThumbnailPath(originalPath, 'lg'),\n getThumbnailPath(originalPath, 'md'),\n getThumbnailPath(originalPath, 'sm'),\n ]\n}\n\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} from './utils'\nimport { getPublicPath, getWorkspacePath } from '../config'\nimport { jsonResponse, streamResponse, createSSEStream } from './utils/response'\nimport { deleteEmptyFolders } from './utils/folders'\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 const fileName = 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 { error: 'Cannot upload to images/ folder. Upload to public/ instead - thumbnails are generated automatically.' },\n { status: 400 }\n )\n }\n\n // Build the meta key\n let imageKey = '/' + (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 = '/' + (relativeDir ? `${relativeDir}/${newFileName}` : newFileName)\n \n while (meta[newKey]) {\n counter++\n newFileName = `${baseName}-${counter}${ext}`\n newKey = '/' + (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\n try {\n const metadata = await sharp(buffer).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({ error: `Failed to upload file: ${message}` }, { status: 500 })\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 \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 // Also delete local thumbnails if not synced\n if (keyEntry && keyEntry.c === undefined) {\n for (const thumbPath of getAllThumbnailPaths(key)) {\n const absoluteThumbPath = getPublicPath(thumbPath)\n try { await fs.unlink(absoluteThumbPath) } catch { /* ignore */ }\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 local thumbnails if not synced\n if (!isPushedToCloud) {\n for (const thumbPath of getAllThumbnailPaths(imageKey)) {\n const absoluteThumbPath = getPublicPath(thumbPath)\n try { await fs.unlink(absoluteThumbPath) } catch { /* ignore */ }\n }\n }\n delete meta[imageKey]\n }\n }\n } catch {\n // File doesn't exist locally - might be synced\n if (entry) {\n // Just remove from meta (file is on CDN)\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 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\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({ error: 'Folder name is required' }, { status: 400 })\n }\n\n const sanitizedName = name.replace(/[<>:\"/\\\\|?*]/g, '').trim()\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({ error: 'A folder with this name already exists' }, { status: 400 })\n } catch {\n // Good - folder doesn't exist\n }\n\n await fs.mkdir(folderPath, { recursive: true })\n\n return jsonResponse({ success: true, path: path.join(safePath, sanitizedName) })\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 try {\n const { oldPath, newName } = await request.json()\n\n if (!oldPath || !newName) {\n return jsonResponse({ error: 'Path and new name are required' }, { status: 400 })\n }\n\n const sanitizedName = newName.replace(/[<>:\"/\\\\|?*]/g, '').trim()\n if (!sanitizedName) {\n return jsonResponse({ error: 'Invalid name' }, { status: 400 })\n }\n\n const safePath = oldPath.replace(/\\.\\./g, '')\n const absoluteOldPath = getWorkspacePath(safePath)\n const parentDir = path.dirname(absoluteOldPath)\n const absoluteNewPath = path.join(parentDir, sanitizedName)\n\n if (!absoluteOldPath.startsWith(getPublicPath())) {\n return jsonResponse({ error: 'Invalid path' }, { status: 400 })\n }\n\n try {\n await fs.access(absoluteOldPath)\n } catch {\n return jsonResponse({ error: 'File or folder not found' }, { status: 404 })\n }\n\n try {\n await fs.access(absoluteNewPath)\n return jsonResponse({ error: 'An item with this name already exists' }, { status: 400 })\n } catch {\n // Good - new path doesn't exist\n }\n\n const stats = await fs.stat(absoluteOldPath)\n const isFile = stats.isFile()\n const isImage = isFile && isImageFile(path.basename(oldPath))\n\n await fs.rename(absoluteOldPath, absoluteNewPath)\n\n if (isImage) {\n const meta = await loadMeta()\n const oldRelativePath = safePath.replace(/^public\\//, '')\n const newRelativePath = path.join(path.dirname(oldRelativePath), sanitizedName)\n const oldKey = '/' + oldRelativePath\n const newKey = '/' + newRelativePath\n\n if (meta[oldKey]) {\n const entry = meta[oldKey]\n\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 // Thumbnail might not exist\n }\n }\n\n delete meta[oldKey]\n meta[newKey] = entry\n }\n\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 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 } = 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 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 = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/$/, '') || ''\n\n const moved: 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 const itemPath = paths[i]\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 ? path.join(destWithoutPublic, itemName) : itemName\n const oldKey = '/' + oldRelativePath\n const newKey = '/' + newRelativePath\n\n sendEvent({\n type: 'progress',\n current: i + 1,\n total,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: itemName,\n })\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 = isInCloud && entry.c !== undefined ? cdnUrls[entry.c] : undefined\n const isRemote = isInCloud && (!r2PublicUrl || fileCdnUrl !== r2PublicUrl)\n const isPushedToR2 = 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 && isImage) {\n // ===== REMOTE IMAGE =====\n const remoteUrl = `${fileCdnUrl}${oldKey}`\n const buffer = await downloadFromRemoteUrl(remoteUrl)\n \n await fs.mkdir(path.dirname(newAbsolutePath), { recursive: true })\n await fs.writeFile(newAbsolutePath, buffer)\n \n const newEntry: MetaEntry = {\n o: entry?.o,\n b: entry?.b,\n }\n delete meta[oldKey]\n meta[newKey] = newEntry\n moved.push(itemPath)\n\n } else if (isPushedToR2 && isImage) {\n // ===== CLOUD IMAGE (R2) =====\n const buffer = await downloadFromCdn(oldKey)\n \n await fs.mkdir(path.dirname(newAbsolutePath), { recursive: true })\n await fs.writeFile(newAbsolutePath, buffer)\n \n let newEntry: MetaEntry = {\n o: entry?.o,\n b: entry?.b,\n }\n \n if (hasProcessedThumbnails) {\n const processedEntry = await processImage(buffer, newKey)\n newEntry = { ...newEntry, ...processedEntry }\n }\n \n await uploadOriginalToCdn(newKey)\n \n if (hasProcessedThumbnails) {\n await uploadToCdn(newKey)\n }\n \n await deleteFromCdn(oldKey, hasProcessedThumbnails)\n \n try { await fs.unlink(newAbsolutePath) } catch { /* ignore */ }\n if (hasProcessedThumbnails) {\n await deleteLocalThumbnails(newKey)\n }\n \n newEntry.c = entry?.c\n \n delete meta[oldKey]\n meta[newKey] = newEntry\n moved.push(itemPath)\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 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\n }\n\n await fs.rename(absolutePath, newAbsolutePath)\n\n const stats = await fs.stat(newAbsolutePath)\n if (stats.isFile() && 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 // 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 } else if (stats.isDirectory()) {\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 }\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 await saveMeta(meta)\n\n // Clean up empty source folders\n for (const folder of sourceFolders) {\n await deleteEmptyFolders(folder)\n }\n\n sendEvent({\n type: 'complete',\n moved: moved.length,\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({ error: 'Destination is required' }, { status: 400 })\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 = 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 ? path.join(destWithoutPublic, itemName) : 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 = isInCloud && entry.c !== undefined ? cdnUrls[entry.c] : undefined\n const isRemote = isInCloud && (!r2PublicUrl || fileCdnUrl !== r2PublicUrl)\n const isPushedToR2 = isInCloud && r2PublicUrl && fileCdnUrl === r2PublicUrl\n const hasProcessedThumbnails = isProcessed(entry)\n\n try {\n if (isRemote && isImage) {\n // ===== REMOTE IMAGE: 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 images 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\n } else if (isPushedToR2 && isImage) {\n // ===== CLOUD IMAGE (R2): Download, move, re-upload, delete old =====\n \n // Download original from R2\n const buffer = await downloadFromCdn(oldKey)\n \n // Save to new local location\n await fs.mkdir(path.dirname(newAbsolutePath), { recursive: true })\n await fs.writeFile(newAbsolutePath, buffer)\n \n // Create new meta entry\n let newEntry: MetaEntry = {\n o: entry?.o,\n b: entry?.b,\n }\n \n // If processed, regenerate thumbnails\n if (hasProcessedThumbnails) {\n const processedEntry = await processImage(buffer, newKey)\n newEntry = { ...newEntry, ...processedEntry }\n }\n \n // Upload original to new R2 location\n await uploadOriginalToCdn(newKey)\n \n // If processed, upload thumbnails to R2\n if (hasProcessedThumbnails) {\n await uploadToCdn(newKey)\n }\n \n // Delete old files from R2\n await deleteFromCdn(oldKey, hasProcessedThumbnails)\n \n // Delete local files (keep cloud-only state)\n try { await fs.unlink(newAbsolutePath) } catch { /* ignore */ }\n if (hasProcessedThumbnails) {\n await deleteLocalThumbnails(newKey)\n }\n \n // Set c to same CDN index\n newEntry.c = entry?.c\n \n // Update meta\n delete meta[oldKey]\n meta[newKey] = newEntry\n metaChanged = true\n moved.push(itemPath)\n\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\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 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 { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'\nimport { jsonResponse } from './utils/response'\nimport { getAllThumbnailPaths, isProcessed } from '../types'\nimport {\n loadMeta,\n saveMeta,\n isImageFile,\n getContentType,\n processImage,\n downloadFromCdn,\n uploadToCdn,\n deleteLocalThumbnails,\n deleteThumbnailsFromCdn,\n getOrAddCdnIndex,\n getFileEntries,\n getMetaEntry,\n getCdnUrls,\n downloadFromRemoteUrl,\n} from './utils'\nimport { getPublicPath } from '../config'\nimport {\n purgeCloudflareCache,\n} from './utils'\nimport { deleteEmptyFolders, cleanupEmptyFoldersRecursive } from './utils/folders'\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 (!accountId || !accessKeyId || !secretAccessKey || !bucketName || !publicUrl) {\n return jsonResponse(\n { error: 'R2 not configured. Set CLOUDFLARE_R2_* environment variables.' },\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 errors: string[] = []\n const urlsToPurge: 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 = entry.c !== undefined ? cdnUrls[entry.c] : undefined\n const isAlreadyInOurR2 = existingCdnUrl === publicUrl\n \n if (isAlreadyInOurR2) {\n pushed.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 urlsToPurge.push(`${publicUrl}${imageKey}`)\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 urlsToPurge.push(`${publicUrl}${thumbPath}`)\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 { await fs.unlink(localPath) } catch { /* ignore */ }\n }\n\n // Delete local original\n try { await fs.unlink(originalLocalPath) } catch { /* ignore */ }\n }\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 await saveMeta(meta)\n \n // Clean up empty source folders\n for (const folder of sourceFolders) {\n await deleteEmptyFolders(folder)\n }\n \n // Purge Cloudflare cache for uploaded files\n let cacheMessage: string | undefined\n if (urlsToPurge.length > 0) {\n const cacheResult = await purgeCloudflareCache(urlsToPurge)\n if (cacheResult.message) {\n cacheMessage = cacheResult.message\n }\n }\n\n return jsonResponse({\n success: true,\n pushed,\n errors: errors.length > 0 ? errors : undefined,\n cacheMessage,\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\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 const urlsToPurge: 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 = existingCdnIndex !== undefined ? cdnUrls[existingCdnIndex] : 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 thumbnails to R2 and clean up local files\n updatedEntry.c = existingCdnIndex\n await uploadToCdn(imageKey)\n \n // Collect URLs to purge\n for (const thumbPath of getAllThumbnailPaths(imageKey)) {\n urlsToPurge.push(`${publicUrl}${thumbPath}`)\n }\n \n await deleteLocalThumbnails(imageKey)\n // Delete local original\n try { await fs.unlink(originalPath) } catch { /* ignore */ }\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 // Purge Cloudflare cache for re-uploaded thumbnails\n let cacheMessage: string | undefined\n if (urlsToPurge.length > 0) {\n const cacheResult = await purgeCloudflareCache(urlsToPurge)\n if (cacheResult.message) {\n cacheMessage = cacheResult.message\n }\n }\n\n return jsonResponse({\n success: true,\n processed,\n errors: errors.length > 0 ? errors : undefined,\n cacheMessage,\n })\n } catch (error) {\n console.error('Failed to reprocess:', error)\n return jsonResponse({ error: 'Failed to reprocess images' }, { status: 500 })\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 try {\n const body = await request.json() as { imageKeys: string[] }\n imageKeys = body.imageKeys\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 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 const urlsToPurge: string[] = []\n\n const total = imageKeys.length\n sendEvent({ type: 'start', total })\n\n for (let i = 0; i < imageKeys.length; i++) {\n let imageKey = imageKeys[i]\n \n // Normalize key to have leading /\n if (!imageKey.startsWith('/')) {\n imageKey = `/${imageKey}`\n }\n \n sendEvent({ \n type: 'progress', \n current: i + 1, \n total, \n percent: Math.round(((i + 1) / total) * 100),\n message: `Removing thumbnails for ${imageKey.slice(1)}...`\n })\n\n try {\n const entry = getMetaEntry(meta, imageKey)\n if (!entry) {\n errors.push(imageKey)\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 continue\n }\n \n const existingCdnIndex = entry.c\n const existingCdnUrl = existingCdnIndex !== undefined ? cdnUrls[existingCdnIndex] : 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 // Collect URLs to purge from cache\n for (const thumbPath of getAllThumbnailPaths(imageKey)) {\n urlsToPurge.push(`${publicUrl}${thumbPath}`)\n }\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 removed.push(imageKey)\n } catch (error) {\n console.error(`Failed to unprocess ${imageKey}:`, error)\n errors.push(imageKey)\n }\n }\n\n sendEvent({ type: 'cleanup', message: 'Saving metadata...' })\n await saveMeta(meta)\n \n let cacheMessage = ''\n if (urlsToPurge.length > 0) {\n sendEvent({ type: 'cleanup', message: 'Purging CDN cache...' })\n const cacheResult = await purgeCloudflareCache(urlsToPurge)\n if (cacheResult.message) {\n cacheMessage = ` ${cacheResult.message}`\n }\n }\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${removed.length !== 1 ? 's' : ''}.`\n if (skipped.length > 0) {\n message += ` ${skipped.length} image${skipped.length !== 1 ? 's' : ''} had no thumbnails.`\n }\n if (errors.length > 0) {\n message += ` ${errors.length} image${errors.length !== 1 ? 's' : ''} failed.`\n }\n message += cacheMessage\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 try {\n const body = await request.json() as { imageKeys: string[] }\n imageKeys = body.imageKeys\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 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 urlsToPurge: string[] = []\n\n const total = imageKeys.length\n sendEvent({ type: 'start', total })\n\n for (let i = 0; i < imageKeys.length; i++) {\n let imageKey = imageKeys[i]\n \n // Normalize key to have leading /\n if (!imageKey.startsWith('/')) {\n imageKey = `/${imageKey}`\n }\n \n sendEvent({ \n type: 'progress', \n current: i + 1, \n total, \n percent: Math.round(((i + 1) / total) * 100),\n message: `Processing ${imageKey.slice(1)}...`\n })\n\n try {\n let buffer: Buffer\n const entry = getMetaEntry(meta, imageKey)\n const existingCdnIndex = entry?.c\n const existingCdnUrl = existingCdnIndex !== undefined ? cdnUrls[existingCdnIndex] : 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('images', imageDir === '.' ? '' : imageDir)\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 await uploadToCdn(imageKey)\n \n for (const thumbPath of getAllThumbnailPaths(imageKey)) {\n urlsToPurge.push(`${publicUrl}${thumbPath}`)\n }\n \n await deleteLocalThumbnails(imageKey)\n try { await fs.unlink(originalPath) } catch { /* ignore */ }\n }\n \n meta[imageKey] = updatedEntry\n }\n \n processed.push(imageKey)\n } catch (error) {\n console.error(`Failed to reprocess ${imageKey}:`, error)\n errors.push(imageKey)\n }\n }\n\n sendEvent({ type: 'cleanup', message: 'Saving metadata...' })\n await saveMeta(meta)\n \n let cacheMessage = ''\n if (urlsToPurge.length > 0) {\n sendEvent({ type: 'cleanup', message: 'Purging CDN cache...' })\n const cacheResult = await purgeCloudflareCache(urlsToPurge)\n if (cacheResult.message) {\n cacheMessage = ` ${cacheResult.message}`\n }\n }\n\n // Build completion message\n let message = `Generated thumbnails for ${processed.length} image${processed.length !== 1 ? 's' : ''}.`\n if (errors.length > 0) {\n message += ` ${errors.length} image${errors.length !== 1 ? 's' : ''} failed.`\n }\n message += cacheMessage\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 const urlsToPurge: 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<{ key: string; entry: import('../types').MetaEntry }> = []\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 = existingCdnIndex !== undefined ? cdnUrls[existingCdnIndex] : 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 sendEvent({ \n type: 'progress', \n current: i + 1, \n total, \n percent: Math.round(((i + 1) / total) * 100),\n currentFile: key.slice(1) // Remove leading /\n })\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('images', imageDir === '.' ? '' : imageDir)\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, upload new thumbnails and clean up local files\n if (isInOurR2) {\n await uploadToCdn(key)\n \n // Collect URLs to purge\n for (const thumbPath of getAllThumbnailPaths(key)) {\n urlsToPurge.push(`${publicUrl}${thumbPath}`)\n }\n \n await deleteLocalThumbnails(key)\n // Delete local original\n try { await fs.unlink(fullPath) } catch { /* ignore */ }\n }\n // Remote images stay local after processing (original + thumbnails)\n\n processed.push(key.slice(1))\n } catch (error) {\n console.error(`Failed to process ${key}:`, error)\n errors.push(key.slice(1))\n }\n }\n\n sendEvent({ type: 'cleanup', message: 'Removing orphaned thumbnails...' })\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(dir: string, relativePath: string = ''): 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 ? `${relativePath}/${fsEntry.name}` : 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(`Failed to remove orphan ${publicPath}:`, err)\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(path.join(dir, fsEntry.name))\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 // Purge Cloudflare cache for re-uploaded thumbnails\n let cacheMessage = ''\n if (urlsToPurge.length > 0) {\n const cacheResult = await purgeCloudflareCache(urlsToPurge)\n if (cacheResult.message) {\n cacheMessage = cacheResult.message\n }\n }\n\n sendEvent({ \n type: 'complete', \n processed: processed.length, \n alreadyProcessed,\n orphansRemoved: orphansRemoved.length,\n errors: errors.length,\n cacheMessage,\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 } = 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 stream = new ReadableStream({\n async start(controller) {\n const encoder = new TextEncoder()\n const sendEvent = (data: Record<string, unknown>) => {\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\\n\\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 try {\n const meta = await loadMeta()\n\n for (let i = 0; i < imageKeys.length; i++) {\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 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 // 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 thumbnails from R2\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 downloaded.push(imageKey)\n sendEvent({\n type: 'progress',\n current: i + 1,\n total: imageKeys.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 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${downloaded.length !== 1 ? 's' : ''}.`\n if (skipped.length > 0) {\n message += ` ${skipped.length} image${skipped.length !== 1 ? 's were' : ' was'} not on cloud.`\n }\n if (errors.length > 0) {\n message += ` ${errors.length} image${errors.length !== 1 ? 's' : ''} 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 (!accountId || !accessKeyId || !secretAccessKey || !bucketName || !publicUrl) {\n sendEvent({ type: 'error', message: 'R2 not configured' })\n controller.close()\n return\n }\n\n const { paths } = await request.json()\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 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 const pushed: string[] = []\n const skipped: string[] = []\n const errors: string[] = []\n const urlsToPurge: string[] = []\n const total = paths.length\n\n sendEvent({ type: 'start', total })\n\n for (let i = 0; i < paths.length; i++) {\n const itemPath = paths[i]\n const key = itemPath.startsWith('public/') ? '/' + itemPath.slice(7) : itemPath\n const entry = meta[key] as { c?: number; u?: 1; o?: { w: number; h: number }; b?: string; sm?: object; md?: object; lg?: object; f?: object } | undefined\n\n sendEvent({\n type: 'progress',\n current: i + 1,\n total,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: path.basename(key),\n })\n\n if (!entry || entry.u !== 1) {\n skipped.push(key)\n continue\n }\n\n // Check if this is an R2 file\n const fileCdnUrl = entry.c !== undefined ? cdnUrls[entry.c]?.replace(/\\/$/, '') : undefined\n if (!fileCdnUrl || fileCdnUrl !== r2PublicUrl) {\n skipped.push(key)\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 // Upload to R2 (overwrite)\n const uploadKey = key.startsWith('/') ? key.slice(1) : key\n await s3.send(new PutObjectCommand({\n Bucket: bucketName,\n Key: uploadKey,\n Body: buffer,\n ContentType: contentType,\n }))\n\n // If image is processed, also update thumbnails\n if (isProcessed(entry)) {\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 // Add thumbnail URLs to purge\n for (const thumbPath of getAllThumbnailPaths(key)) {\n urlsToPurge.push(`${publicUrl}${thumbPath}`)\n }\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 // Add original URL to purge\n urlsToPurge.push(`${publicUrl}${key}`)\n\n pushed.push(key)\n } catch (error) {\n console.error(`Failed to push update for ${key}:`, error)\n errors.push(key)\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 cacheMessage = ''\n if (urlsToPurge.length > 0) {\n sendEvent({ type: 'cleanup', message: 'Purging CDN cache...' })\n const cacheResult = await purgeCloudflareCache(urlsToPurge)\n if (cacheResult.status === 'not_configured') {\n cacheMessage = ` ${cacheResult.message}`\n } else if (cacheResult.status === 'failed') {\n cacheMessage = ` ${cacheResult.message}`\n } else if (cacheResult.status === 'success' && cacheResult.message) {\n cacheMessage = ` ${cacheResult.message}`\n }\n }\n\n let message = `Pushed ${pushed.length} update${pushed.length !== 1 ? 's' : ''} to cloud.`\n if (skipped.length > 0) {\n message += ` ${skipped.length} file${skipped.length !== 1 ? 's' : ''} skipped.`\n }\n if (errors.length > 0) {\n message += ` ${errors.length} file${errors.length !== 1 ? 's' : ''} failed.`\n }\n message += cacheMessage\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 pending updates (delete local files, keep cloud versions)\n */\nexport async function handleCancelUpdates(request: Request) {\n try {\n const { paths } = await request.json()\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 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/') ? '/' + itemPath.slice(7) : 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\n/**\n * Clear CDN cache for selected files\n */\nexport async function handleClearCache(request: Request) {\n const publicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/$/, '')\n \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 cdnUrls = getCdnUrls(meta)\n const urlsToPurge: string[] = []\n\n for (const itemPath of paths) {\n const key = itemPath.startsWith('public/') ? '/' + itemPath.slice(7) : itemPath\n const entry = meta[key] as { c?: number; sm?: object; md?: object; lg?: object; f?: object } | undefined\n \n if (!entry || entry.c === undefined) continue\n \n const cdnUrl = cdnUrls[entry.c]?.replace(/\\/$/, '')\n if (!cdnUrl) continue\n \n // Add original URL\n urlsToPurge.push(`${cdnUrl}${key}`)\n \n // Add thumbnail URLs if they exist\n if (entry.sm || entry.md || entry.lg || entry.f) {\n for (const thumbPath of getAllThumbnailPaths(key)) {\n urlsToPurge.push(`${cdnUrl}${thumbPath}`)\n }\n }\n }\n\n if (urlsToPurge.length === 0) {\n return jsonResponse({\n success: true,\n message: 'No CDN files to clear cache for.',\n })\n }\n\n const cacheResult = await purgeCloudflareCache(urlsToPurge)\n \n return jsonResponse({\n success: cacheResult.status === 'success',\n message: cacheResult.message || `Cleared cache for ${urlsToPurge.length} URLs.`,\n })\n } catch (error) {\n console.error('Clear cache error:', error)\n return jsonResponse({ error: 'Failed to clear cache' }, { 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 { encode } from 'blurhash'\nimport { loadMeta, saveMeta, isMediaFile, isImageFile, getFileEntries } from './utils'\nimport { getAllThumbnailPaths, isProcessed } from '../types'\nimport { getPublicPath } from '../config'\nimport { deleteEmptyFolders, cleanupEmptyFoldersRecursive } 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(k => !k.startsWith('_')).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(dir: string, relativePath: 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 \n const fullPath = path.join(dir, entry.name)\n const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name\n\n // Skip the images folder (generated thumbnails)\n if (relPath === 'images' || relPath.startsWith('images/')) 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 // 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 and generate blurhash 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 }, b: '' }\n } else {\n try {\n const buffer = await fs.readFile(fullPath)\n const metadata = await sharp(buffer).metadata()\n \n // Generate blurhash\n const { data, info } = await sharp(buffer)\n .resize(32, 32, { fit: 'inside' })\n .ensureAlpha()\n .raw()\n .toBuffer({ resolveWithObject: true })\n \n const blurhash = encode(new Uint8ClampedArray(data), info.width, info.height, 4, 4)\n \n meta[imageKey] = {\n o: { w: metadata.width || 0, h: metadata.height || 0 },\n b: blurhash,\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 } 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({ type: 'cleanup', message: 'Checking for orphaned thumbnails...' })\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(dir: string, relativePath: 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\n const fullPath = path.join(dir, entry.name)\n const relPath = relativePath ? `${relativePath}/${entry.name}` : 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 \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 await deleteEmptyFolders(fullPath)\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 try {\n await cleanupEmptyFoldersRecursive(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({ type: 'cleanup', message: 'Checking for orphaned entries...' })\n const orphanedEntries: string[] = []\n const cdnUrls = (meta._cdns || []) as string[]\n const r2PublicUrl = (process.env.CLOUDFLARE_R2_PUBLIC_URL || '').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({ type: 'cleanup', message: `Removed ${orphanedEntries.length} orphaned entries...` })\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 })\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({ error: 'Failed to delete orphaned files' }, { status: 500 })\n }\n}\n","import sharp from 'sharp'\nimport { encode } from 'blurhash'\nimport {\n loadMeta,\n saveMeta,\n getOrAddCdnIndex,\n getMetaEntry,\n setMetaEntry,\n} from './utils'\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 + blurhash\n */\nasync function processRemoteImage(url: string): 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 const metadata = await sharp(buffer).metadata()\n \n // Generate blurhash\n const { data, info } = await sharp(buffer)\n .resize(32, 32, { fit: 'inside' })\n .ensureAlpha()\n .raw()\n .toBuffer({ resolveWithObject: true })\n \n const blurhash = encode(new Uint8ClampedArray(data), info.width, info.height, 4, 4)\n \n return {\n o: { w: metadata.width || 0, h: metadata.height || 0 },\n b: blurhash,\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 controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\\n\\n`))\n }\n\n try {\n const { urls } = await request.json() as { urls: string[] }\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 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 const url = urls[i].trim()\n if (!url) continue\n \n sendEvent({\n type: 'progress',\n current: i + 1,\n total,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: url,\n })\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 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 added.push(path)\n } catch (error) {\n console.error(`Failed to import ${url}:`, error)\n errors.push(url)\n }\n }\n\n await saveMeta(meta)\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 error: 'Source file must be named favicon.png or favicon.jpg' \n }, { status: 400 })\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\n let metadata\n try {\n metadata = await sharp(sourcePath).metadata()\n } catch {\n return jsonResponse({ error: 'Source file is not a valid image' }, { status: 400 })\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 error: 'Output directory src/app/ not found' \n }, { status: 500 })\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 sendEvent({ \n type: 'progress', \n current: i + 1, \n total, \n percent: Math.round(((i + 1) / total) * 100),\n message: `Generating ${config.name} (${config.size}x${config.size})...`\n })\n\n try {\n const outputPath = path.join(outputDir, config.name)\n \n await sharp(sourcePath)\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 } catch (error) {\n console.error(`Failed to generate ${config.name}:`, error)\n errors.push(config.name)\n }\n }\n\n // Build completion message\n let message = `Generated ${generated.length} favicon${generated.length !== 1 ? 's' : ''} 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"],"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","path","fs","sharp","fs","path","S3Client","PutObjectCommand","S3Client","fs","PutObjectCommand","path","fs","path","fs","path","S3Client","PutObjectCommand","fs","path","sharp","encode","fs","path","sharp","encode","localPath","sharp","encode","path","sharp","encode","sharp","path","fs","path","fs","sharp","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"],"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 { handleList, handleSearch, handleListFolders, handleCountImages, handleFolderImages } from '../handlers/list'\nimport { handleUpload, handleDelete, handleCreateFolder, handleRename, handleMoveStream } from '../handlers/files'\nimport { handleSync, handleReprocessStream, handleUnprocessStream, handleDownloadStream, handlePushUpdatesStream, handleCancelUpdates } from '../handlers/images'\nimport { handleScanStream, handleDeleteOrphans } from '../handlers/scan'\nimport { handleImportUrls, handleGetCdns, handleUpdateCdns } from '../handlers/import'\nimport { handleGenerateFavicon } from '../handlers/favicon'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = dirname(__filename)\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(startPort: number, maxAttempts = 10): 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(`No available port found between ${startPort} and ${startPort + maxAttempts - 1}`)\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.studio only\n const envStudioPath = join(workspace, '.env.studio')\n \n if (existsSync(envStudioPath)) {\n loadEnv({ path: envStudioPath, quiet: true })\n }\n\n // Middleware - skip JSON parsing for upload route (needs raw body for FormData)\n app.use((req, res, next) => {\n if (req.path === '/api/studio/upload') {\n next()\n } else {\n express.json({ limit: '50mb' })(req, res, next)\n }\n })\n app.use((req, res, next) => {\n if (req.path === '/api/studio/upload') {\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\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/move', wrapHandler(handleMoveStream, true))\n app.post('/api/studio/sync', wrapHandler(handleSync, true))\n app.post('/api/studio/reprocess-stream', wrapHandler(handleReprocessStream, true))\n app.post('/api/studio/unprocess-stream', wrapHandler(handleUnprocessStream, true))\n app.post('/api/studio/download-stream', wrapHandler(handleDownloadStream, true))\n app.post('/api/studio/push-updates-stream', wrapHandler(handlePushUpdatesStream, true))\n app.post('/api/studio/cancel-updates', wrapHandler(handleCancelUpdates))\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('/api/studio/generate-favicon', wrapHandler(handleGenerateFavicon, true))\n\n // API Routes - DELETE endpoints\n app.post('/api/studio/delete', wrapHandler(handleDelete))\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 app.listen(port, () => {\n console.log(`\n┌─────────────────────────────────────┐\n│ Studio - Media Manager │\n├─────────────────────────────────────┤\n│ Workspace: ${workspace.length > 24 ? '...' + workspace.slice(-21) : workspace.padEnd(24)}│\n│ URL: http://localhost:${port} │\n└─────────────────────────────────────┘\n`)\n\n if (open) {\n import('open').then((mod) => {\n mod.default(`http://localhost:${port}`)\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(req: Request): 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(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 // 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(originalPath: string, entry: MetaEntry): Array<{ path: string; size: 'f' | 'lg' | 'md' | 'sm' }> {\n const thumbnails: Array<{ path: string; size: 'f' | 'lg' | 'md' | 'sm' }> = []\n \n if (entry.f) {\n thumbnails.push({ path: getThumbnailPath(originalPath, 'full'), size: 'f' })\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): { cloudCount: number; remoteCount: number; localCount: number; updateCount: number } {\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 = 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 = 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<{ path: string; size: 'f' | 'lg' | 'md' | 'sm'; originalKey: string }> = []\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(([k]) => k === thumb.originalKey)?.[1]\n const cdnIndex = originalEntry?.c\n const cdnBaseUrl = cdnIndex !== undefined ? cdnUrls[cdnIndex] : undefined\n // Build the full thumbnail URL (with CDN base if applicable)\n const thumbnailUrl = cdnBaseUrl ? `${cdnBaseUrl}${thumb.path}` : 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 = isPushedToCloud && normalizedCdnBaseUrl !== r2PublicUrl\n \n // Get dimensions for this thumbnail size\n const thumbDims = originalEntry?.[thumb.size]\n const dimensions = thumbDims ? { width: thumbDims.w, height: thumbDims.h } : 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 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 (!thumbRelative.startsWith(imagesSubPath + '/') && thumbRelative !== imagesSubPath) 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 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 ? `public/${relativePath}/${entry.name}` : `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(key, metaEntry).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 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 = pathPrefix === '/' ? `/${entry.name}/` : `${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(folderPrefix, fileEntries, cdnUrls, r2PublicUrl)\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 = 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 = pathPrefix === '/' ? `/${folderName}/` : `${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(folderPrefix, fileEntries, cdnUrls, r2PublicUrl)\n \n items.push({\n name: folderName,\n path: relativePath ? `public/${relativePath}/${folderName}` : `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 = isPushedToCloud && entry.c !== undefined ? cdnUrls[entry.c] : undefined\n const normalizedFileCdnUrl = fileCdnUrl?.replace(/\\/$/, '') || ''\n const isRemote = isPushedToCloud && (!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 - use thumbnail\n const thumbPath = getThumbnailPath(key, 'sm')\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\n thumbnail = key\n hasThumbnail = false\n }\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 ? `public/${relativePath}/${fileName}` : `public/${fileName}`,\n type: 'file',\n size: fileSize,\n thumbnail,\n hasThumbnail,\n isProcessed: entryIsProcessed,\n cdnPushed: isPushedToCloud,\n cdnBaseUrl: fileCdnUrl,\n isRemote,\n isProtected: isInsideImagesFolder,\n dimensions: entry.o ? { width: entry.o.w, height: entry.o.h } : 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 \n if (query.length < 2) {\n return jsonResponse({ items: [] })\n }\n\n try {\n const meta = await loadMeta()\n const fileEntries = getFileEntries(meta)\n const cdnUrls = getCdnUrls(meta)\n const r2PublicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/$/, '') || ''\n const items: FileItem[] = []\n\n for (const [key, entry] of fileEntries) {\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 = isPushedToCloud && entry.c !== undefined ? cdnUrls[entry.c] : undefined\n const normalizedFileCdnUrl = fileCdnUrl?.replace(/\\/$/, '') || ''\n const isRemote = isPushedToCloud && (!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 - use thumbnail\n const thumbPath = getThumbnailPath(key, 'sm')\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 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 cdnPushed: isPushedToCloud,\n cdnBaseUrl: fileCdnUrl,\n isRemote,\n dimensions: entry.o ? { width: entry.o.w, height: entry.o.h } : 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 (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'images') {\n const folderRelPath = relativePath ? `${relativePath}/${entry.name}` : 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({ error: 'Failed to get folder files' }, { status: 500 })\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\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 { encode } from 'blurhash'\nimport type { MetaEntry, Dimensions } from '../../types'\nimport { getPublicPath } from '../../config'\n\nexport const FULL_MAX_WIDTH = 2560\n\nexport const DEFAULT_SIZES: Record<string, { width: number; suffix: string; key: 'sm' | 'md' | 'lg' }> = {\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 const sharpInstance = sharp(buffer)\n const metadata = await sharpInstance.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('/') ? imageKey.slice(1) : imageKey\n const baseName = path.basename(keyWithoutSlash, path.extname(keyWithoutSlash))\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 = imageDir === '.' ? `${baseName}${outputExt}` : `${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(buffer).resize(fullWidth, fullHeight).png({ quality: 85 }).toFile(fullPath)\n } else {\n await sharp(buffer).resize(fullWidth, fullHeight).jpeg({ quality: 85 }).toFile(fullPath)\n }\n } else {\n if (isPng) {\n await sharp(buffer).png({ quality: 85 }).toFile(fullPath)\n } else {\n await sharp(buffer).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 = imageDir === '.' ? sizeFileName : `${imageDir}/${sizeFileName}`\n const sizePath = getPublicPath('images', sizeFilePath)\n\n if (isPng) {\n await sharp(buffer).resize(maxWidth, newHeight).png({ quality: 80 }).toFile(sizePath)\n } else {\n await sharp(buffer).resize(maxWidth, newHeight).jpeg({ quality: 80 }).toFile(sizePath)\n }\n\n entry[key] = { w: maxWidth, h: newHeight }\n }\n\n // Generate blurhash\n const { data, info } = await sharp(buffer)\n .resize(32, 32, { fit: 'inside' })\n .ensureAlpha()\n .raw()\n .toBuffer({ resolveWithObject: true })\n\n entry.b = encode(new Uint8ClampedArray(data), info.width, info.height, 4, 4)\n\n return entry\n}\n","import { promises as fs } from 'fs'\nimport { S3Client, GetObjectCommand, PutObjectCommand, DeleteObjectCommand } 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 * 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, b: blurhash, 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 b?: string // blurhash\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}\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}, b: \"...\", 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 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 // 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(originalPath: string, size: 'sm' | 'md' | 'lg' | 'full'): string {\n if (size === 'full') {\n const ext = originalPath.match(/\\.\\w+$/)?.[0] || '.jpg'\n const base = originalPath.replace(/\\.\\w+$/, '')\n const outputExt = ext.toLowerCase() === '.png' ? '.png' : '.jpg'\n return `/images${base}${outputExt}`\n }\n const ext = originalPath.match(/\\.\\w+$/)?.[0] || '.jpg'\n const base = originalPath.replace(/\\.\\w+$/, '')\n const outputExt = ext.toLowerCase() === '.png' ? '.png' : '.jpg'\n return `/images${base}-${size}${outputExt}`\n}\n\n/**\n * Get all thumbnail paths for an image\n */\nexport function getAllThumbnailPaths(originalPath: string): string[] {\n return [\n getThumbnailPath(originalPath, 'full'),\n getThumbnailPath(originalPath, 'lg'),\n getThumbnailPath(originalPath, 'md'),\n getThumbnailPath(originalPath, 'sm'),\n ]\n}\n\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} from './utils'\nimport { getPublicPath, getWorkspacePath } from '../config'\nimport { jsonResponse, streamResponse, createSSEStream } from './utils/response'\nimport { deleteEmptyFolders } from './utils/folders'\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 const fileName = 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 { error: 'Cannot upload to images/ folder. Upload to public/ instead - thumbnails are generated automatically.' },\n { status: 400 }\n )\n }\n\n // Build the meta key\n let imageKey = '/' + (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 = '/' + (relativeDir ? `${relativeDir}/${newFileName}` : newFileName)\n \n while (meta[newKey]) {\n counter++\n newFileName = `${baseName}-${counter}${ext}`\n newKey = '/' + (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\n try {\n const metadata = await sharp(buffer).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({ error: `Failed to upload file: ${message}` }, { status: 500 })\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 \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 // Also delete local thumbnails if not synced\n if (keyEntry && keyEntry.c === undefined) {\n for (const thumbPath of getAllThumbnailPaths(key)) {\n const absoluteThumbPath = getPublicPath(thumbPath)\n try { await fs.unlink(absoluteThumbPath) } catch { /* ignore */ }\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 local thumbnails if not synced\n if (!isPushedToCloud) {\n for (const thumbPath of getAllThumbnailPaths(imageKey)) {\n const absoluteThumbPath = getPublicPath(thumbPath)\n try { await fs.unlink(absoluteThumbPath) } catch { /* ignore */ }\n }\n }\n delete meta[imageKey]\n }\n }\n } catch {\n // File doesn't exist locally - might be synced\n if (entry) {\n // Just remove from meta (file is on CDN)\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 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\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({ error: 'Folder name is required' }, { status: 400 })\n }\n\n const sanitizedName = name.replace(/[<>:\"/\\\\|?*]/g, '').trim()\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({ error: 'A folder with this name already exists' }, { status: 400 })\n } catch {\n // Good - folder doesn't exist\n }\n\n await fs.mkdir(folderPath, { recursive: true })\n\n return jsonResponse({ success: true, path: path.join(safePath, sanitizedName) })\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 try {\n const { oldPath, newName } = await request.json()\n\n if (!oldPath || !newName) {\n return jsonResponse({ error: 'Path and new name are required' }, { status: 400 })\n }\n\n const sanitizedName = newName.replace(/[<>:\"/\\\\|?*]/g, '').trim()\n if (!sanitizedName) {\n return jsonResponse({ error: 'Invalid name' }, { status: 400 })\n }\n\n const safePath = oldPath.replace(/\\.\\./g, '')\n const absoluteOldPath = getWorkspacePath(safePath)\n const parentDir = path.dirname(absoluteOldPath)\n const absoluteNewPath = path.join(parentDir, sanitizedName)\n\n if (!absoluteOldPath.startsWith(getPublicPath())) {\n return jsonResponse({ error: 'Invalid path' }, { status: 400 })\n }\n\n try {\n await fs.access(absoluteOldPath)\n } catch {\n return jsonResponse({ error: 'File or folder not found' }, { status: 404 })\n }\n\n try {\n await fs.access(absoluteNewPath)\n return jsonResponse({ error: 'An item with this name already exists' }, { status: 400 })\n } catch {\n // Good - new path doesn't exist\n }\n\n const stats = await fs.stat(absoluteOldPath)\n const isFile = stats.isFile()\n const isImage = isFile && isImageFile(path.basename(oldPath))\n\n await fs.rename(absoluteOldPath, absoluteNewPath)\n\n if (isImage) {\n const meta = await loadMeta()\n const oldRelativePath = safePath.replace(/^public\\//, '')\n const newRelativePath = path.join(path.dirname(oldRelativePath), sanitizedName)\n const oldKey = '/' + oldRelativePath\n const newKey = '/' + newRelativePath\n\n if (meta[oldKey]) {\n const entry = meta[oldKey]\n\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 // Thumbnail might not exist\n }\n }\n\n delete meta[oldKey]\n meta[newKey] = entry\n }\n\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 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 } = 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 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 = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\\/$/, '') || ''\n\n const moved: 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 const itemPath = paths[i]\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 ? path.join(destWithoutPublic, itemName) : itemName\n const oldKey = '/' + oldRelativePath\n const newKey = '/' + newRelativePath\n\n sendEvent({\n type: 'progress',\n current: i + 1,\n total,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: itemName,\n })\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 = isInCloud && entry.c !== undefined ? cdnUrls[entry.c] : undefined\n const isRemote = isInCloud && (!r2PublicUrl || fileCdnUrl !== r2PublicUrl)\n const isPushedToR2 = 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 && isImage) {\n // ===== REMOTE IMAGE =====\n const remoteUrl = `${fileCdnUrl}${oldKey}`\n const buffer = await downloadFromRemoteUrl(remoteUrl)\n \n await fs.mkdir(path.dirname(newAbsolutePath), { recursive: true })\n await fs.writeFile(newAbsolutePath, buffer)\n \n const newEntry: MetaEntry = {\n o: entry?.o,\n b: entry?.b,\n }\n delete meta[oldKey]\n meta[newKey] = newEntry\n moved.push(itemPath)\n\n } else if (isPushedToR2 && isImage) {\n // ===== CLOUD IMAGE (R2) =====\n const buffer = await downloadFromCdn(oldKey)\n \n await fs.mkdir(path.dirname(newAbsolutePath), { recursive: true })\n await fs.writeFile(newAbsolutePath, buffer)\n \n let newEntry: MetaEntry = {\n o: entry?.o,\n b: entry?.b,\n }\n \n if (hasProcessedThumbnails) {\n const processedEntry = await processImage(buffer, newKey)\n newEntry = { ...newEntry, ...processedEntry }\n }\n \n await uploadOriginalToCdn(newKey)\n \n if (hasProcessedThumbnails) {\n await uploadToCdn(newKey)\n }\n \n await deleteFromCdn(oldKey, hasProcessedThumbnails)\n \n try { await fs.unlink(newAbsolutePath) } catch { /* ignore */ }\n if (hasProcessedThumbnails) {\n await deleteLocalThumbnails(newKey)\n }\n \n newEntry.c = entry?.c\n \n delete meta[oldKey]\n meta[newKey] = newEntry\n moved.push(itemPath)\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 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\n }\n\n await fs.rename(absolutePath, newAbsolutePath)\n\n const stats = await fs.stat(newAbsolutePath)\n if (stats.isFile() && 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 // 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 } else if (stats.isDirectory()) {\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 }\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 await saveMeta(meta)\n\n // Clean up empty source folders\n for (const folder of sourceFolders) {\n await deleteEmptyFolders(folder)\n }\n\n sendEvent({\n type: 'complete',\n moved: moved.length,\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({ error: 'Destination is required' }, { status: 400 })\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 = 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 ? path.join(destWithoutPublic, itemName) : 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 = isInCloud && entry.c !== undefined ? cdnUrls[entry.c] : undefined\n const isRemote = isInCloud && (!r2PublicUrl || fileCdnUrl !== r2PublicUrl)\n const isPushedToR2 = isInCloud && r2PublicUrl && fileCdnUrl === r2PublicUrl\n const hasProcessedThumbnails = isProcessed(entry)\n\n try {\n if (isRemote && isImage) {\n // ===== REMOTE IMAGE: 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 images 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\n } else if (isPushedToR2 && isImage) {\n // ===== CLOUD IMAGE (R2): Download, move, re-upload, delete old =====\n \n // Download original from R2\n const buffer = await downloadFromCdn(oldKey)\n \n // Save to new local location\n await fs.mkdir(path.dirname(newAbsolutePath), { recursive: true })\n await fs.writeFile(newAbsolutePath, buffer)\n \n // Create new meta entry\n let newEntry: MetaEntry = {\n o: entry?.o,\n b: entry?.b,\n }\n \n // If processed, regenerate thumbnails\n if (hasProcessedThumbnails) {\n const processedEntry = await processImage(buffer, newKey)\n newEntry = { ...newEntry, ...processedEntry }\n }\n \n // Upload original to new R2 location\n await uploadOriginalToCdn(newKey)\n \n // If processed, upload thumbnails to R2\n if (hasProcessedThumbnails) {\n await uploadToCdn(newKey)\n }\n \n // Delete old files from R2\n await deleteFromCdn(oldKey, hasProcessedThumbnails)\n \n // Delete local files (keep cloud-only state)\n try { await fs.unlink(newAbsolutePath) } catch { /* ignore */ }\n if (hasProcessedThumbnails) {\n await deleteLocalThumbnails(newKey)\n }\n \n // Set c to same CDN index\n newEntry.c = entry?.c\n \n // Update meta\n delete meta[oldKey]\n meta[newKey] = newEntry\n metaChanged = true\n moved.push(itemPath)\n\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\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 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 { S3Client, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3'\nimport { jsonResponse } from './utils/response'\nimport { getAllThumbnailPaths, isProcessed } from '../types'\nimport {\n loadMeta,\n saveMeta,\n isImageFile,\n getContentType,\n processImage,\n downloadFromCdn,\n uploadToCdn,\n deleteLocalThumbnails,\n deleteThumbnailsFromCdn,\n getOrAddCdnIndex,\n getFileEntries,\n getMetaEntry,\n getCdnUrls,\n downloadFromRemoteUrl,\n} from './utils'\nimport { getPublicPath } from '../config'\nimport { deleteEmptyFolders, cleanupEmptyFoldersRecursive } from './utils/folders'\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 (!accountId || !accessKeyId || !secretAccessKey || !bucketName || !publicUrl) {\n return jsonResponse(\n { error: 'R2 not configured. Set CLOUDFLARE_R2_* environment variables.' },\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 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 = entry.c !== undefined ? cdnUrls[entry.c] : undefined\n const isAlreadyInOurR2 = existingCdnUrl === publicUrl\n \n if (isAlreadyInOurR2) {\n pushed.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 { await fs.unlink(localPath) } catch { /* ignore */ }\n }\n\n // Delete local original\n try { await fs.unlink(originalLocalPath) } catch { /* ignore */ }\n }\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 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 pushed,\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\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 = existingCdnIndex !== undefined ? cdnUrls[existingCdnIndex] : 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 thumbnails to R2 and clean up local files\n updatedEntry.c = existingCdnIndex\n // Delete existing thumbnails from CDN first to clear cache\n await deleteThumbnailsFromCdn(imageKey)\n await uploadToCdn(imageKey)\n \n await deleteLocalThumbnails(imageKey)\n // Delete local original\n try { await fs.unlink(originalPath) } catch { /* ignore */ }\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({ error: 'Failed to reprocess images' }, { status: 500 })\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 try {\n const body = await request.json() as { imageKeys: string[] }\n imageKeys = body.imageKeys\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 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 let imageKey = imageKeys[i]\n \n // Normalize key to have leading /\n if (!imageKey.startsWith('/')) {\n imageKey = `/${imageKey}`\n }\n \n sendEvent({ \n type: 'progress', \n current: i + 1, \n total, \n percent: Math.round(((i + 1) / total) * 100),\n message: `Removing thumbnails for ${imageKey.slice(1)}...`\n })\n\n try {\n const entry = getMetaEntry(meta, imageKey)\n if (!entry) {\n errors.push(imageKey)\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 continue\n }\n \n const existingCdnIndex = entry.c\n const existingCdnUrl = existingCdnIndex !== undefined ? cdnUrls[existingCdnIndex] : 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 removed.push(imageKey)\n } catch (error) {\n console.error(`Failed to unprocess ${imageKey}:`, error)\n errors.push(imageKey)\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${removed.length !== 1 ? 's' : ''}.`\n if (skipped.length > 0) {\n message += ` ${skipped.length} image${skipped.length !== 1 ? 's' : ''} had no thumbnails.`\n }\n if (errors.length > 0) {\n message += ` ${errors.length} image${errors.length !== 1 ? 's' : ''} 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 try {\n const body = await request.json() as { imageKeys: string[] }\n imageKeys = body.imageKeys\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 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 let imageKey = imageKeys[i]\n \n // Normalize key to have leading /\n if (!imageKey.startsWith('/')) {\n imageKey = `/${imageKey}`\n }\n \n sendEvent({ \n type: 'progress', \n current: i + 1, \n total, \n percent: Math.round(((i + 1) / total) * 100),\n message: `Processing ${imageKey.slice(1)}...`\n })\n\n try {\n let buffer: Buffer\n const entry = getMetaEntry(meta, imageKey)\n const existingCdnIndex = entry?.c\n const existingCdnUrl = existingCdnIndex !== undefined ? cdnUrls[existingCdnIndex] : 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('images', imageDir === '.' ? '' : imageDir)\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 existing thumbnails from CDN first to clear cache\n await deleteThumbnailsFromCdn(imageKey)\n await uploadToCdn(imageKey)\n \n await deleteLocalThumbnails(imageKey)\n try { await fs.unlink(originalPath) } catch { /* ignore */ }\n }\n \n meta[imageKey] = updatedEntry\n }\n \n processed.push(imageKey)\n } catch (error) {\n console.error(`Failed to reprocess ${imageKey}:`, error)\n errors.push(imageKey)\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${processed.length !== 1 ? 's' : ''}.`\n if (errors.length > 0) {\n message += ` ${errors.length} image${errors.length !== 1 ? 's' : ''} 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<{ key: string; entry: import('../types').MetaEntry }> = []\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 = existingCdnIndex !== undefined ? cdnUrls[existingCdnIndex] : 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 sendEvent({ \n type: 'progress', \n current: i + 1, \n total, \n percent: Math.round(((i + 1) / total) * 100),\n currentFile: key.slice(1) // Remove leading /\n })\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('images', imageDir === '.' ? '' : imageDir)\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, upload new thumbnails and clean up local files\n if (isInOurR2) {\n // Delete existing thumbnails from CDN first to clear cache\n await deleteThumbnailsFromCdn(key)\n await uploadToCdn(key)\n \n await deleteLocalThumbnails(key)\n // Delete local original\n try { await fs.unlink(fullPath) } catch { /* ignore */ }\n }\n // Remote images stay local after processing (original + thumbnails)\n\n processed.push(key.slice(1))\n } catch (error) {\n console.error(`Failed to process ${key}:`, error)\n errors.push(key.slice(1))\n }\n }\n\n sendEvent({ type: 'cleanup', message: 'Removing orphaned thumbnails...' })\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(dir: string, relativePath: string = ''): 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 ? `${relativePath}/${fsEntry.name}` : 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(`Failed to remove orphan ${publicPath}:`, err)\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(path.join(dir, fsEntry.name))\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 } = 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 stream = new ReadableStream({\n async start(controller) {\n const encoder = new TextEncoder()\n const sendEvent = (data: Record<string, unknown>) => {\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\\n\\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 try {\n const meta = await loadMeta()\n\n for (let i = 0; i < imageKeys.length; i++) {\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 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 // 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 thumbnails from R2\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 downloaded.push(imageKey)\n sendEvent({\n type: 'progress',\n current: i + 1,\n total: imageKeys.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 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${downloaded.length !== 1 ? 's' : ''}.`\n if (skipped.length > 0) {\n message += ` ${skipped.length} image${skipped.length !== 1 ? 's were' : ' was'} not on cloud.`\n }\n if (errors.length > 0) {\n message += ` ${errors.length} image${errors.length !== 1 ? 's' : ''} 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 (!accountId || !accessKeyId || !secretAccessKey || !bucketName || !publicUrl) {\n sendEvent({ type: 'error', message: 'R2 not configured' })\n controller.close()\n return\n }\n\n const { paths } = await request.json()\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 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 const pushed: string[] = []\n const skipped: string[] = []\n const errors: string[] = []\n const total = paths.length\n\n sendEvent({ type: 'start', total })\n\n for (let i = 0; i < paths.length; i++) {\n const itemPath = paths[i]\n const key = itemPath.startsWith('public/') ? '/' + itemPath.slice(7) : itemPath\n const entry = meta[key] as { c?: number; u?: 1; o?: { w: number; h: number }; b?: string; sm?: object; md?: object; lg?: object; f?: object } | undefined\n\n sendEvent({\n type: 'progress',\n current: i + 1,\n total,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: path.basename(key),\n })\n\n if (!entry || entry.u !== 1) {\n skipped.push(key)\n continue\n }\n\n // Check if this is an R2 file\n const fileCdnUrl = entry.c !== undefined ? cdnUrls[entry.c]?.replace(/\\/$/, '') : undefined\n if (!fileCdnUrl || fileCdnUrl !== r2PublicUrl) {\n skipped.push(key)\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(new DeleteObjectCommand({\n Bucket: bucketName,\n Key: uploadKey,\n }))\n } catch {\n // Ignore delete errors - file might not exist\n }\n\n // Upload to R2\n await s3.send(new PutObjectCommand({\n Bucket: bucketName,\n Key: uploadKey,\n Body: buffer,\n ContentType: contentType,\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 pushed.push(key)\n } catch (error) {\n console.error(`Failed to push update for ${key}:`, error)\n errors.push(key)\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${pushed.length !== 1 ? 's' : ''} to cloud.`\n if (skipped.length > 0) {\n message += ` ${skipped.length} file${skipped.length !== 1 ? 's' : ''} skipped.`\n }\n if (errors.length > 0) {\n message += ` ${errors.length} file${errors.length !== 1 ? 's' : ''} 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 pending updates (delete local files, keep cloud versions)\n */\nexport async function handleCancelUpdates(request: Request) {\n try {\n const { paths } = await request.json()\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 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/') ? '/' + itemPath.slice(7) : 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 { encode } from 'blurhash'\nimport { loadMeta, saveMeta, isMediaFile, isImageFile, getFileEntries } from './utils'\nimport { getAllThumbnailPaths, isProcessed } from '../types'\nimport { getPublicPath } from '../config'\nimport { deleteEmptyFolders, cleanupEmptyFoldersRecursive } 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(k => !k.startsWith('_')).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(dir: string, relativePath: 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 \n const fullPath = path.join(dir, entry.name)\n const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name\n\n // Skip the images folder (generated thumbnails)\n if (relPath === 'images' || relPath.startsWith('images/')) 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 // 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 and generate blurhash 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 }, b: '' }\n } else {\n try {\n const buffer = await fs.readFile(fullPath)\n const metadata = await sharp(buffer).metadata()\n \n // Generate blurhash\n const { data, info } = await sharp(buffer)\n .resize(32, 32, { fit: 'inside' })\n .ensureAlpha()\n .raw()\n .toBuffer({ resolveWithObject: true })\n \n const blurhash = encode(new Uint8ClampedArray(data), info.width, info.height, 4, 4)\n \n meta[imageKey] = {\n o: { w: metadata.width || 0, h: metadata.height || 0 },\n b: blurhash,\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 } 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({ type: 'cleanup', message: 'Checking for orphaned thumbnails...' })\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(dir: string, relativePath: 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\n const fullPath = path.join(dir, entry.name)\n const relPath = relativePath ? `${relativePath}/${entry.name}` : 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 \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 await deleteEmptyFolders(fullPath)\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 try {\n await cleanupEmptyFoldersRecursive(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({ type: 'cleanup', message: 'Checking for orphaned entries...' })\n const orphanedEntries: string[] = []\n const cdnUrls = (meta._cdns || []) as string[]\n const r2PublicUrl = (process.env.CLOUDFLARE_R2_PUBLIC_URL || '').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({ type: 'cleanup', message: `Removed ${orphanedEntries.length} orphaned entries...` })\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 })\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({ error: 'Failed to delete orphaned files' }, { status: 500 })\n }\n}\n","import sharp from 'sharp'\nimport { encode } from 'blurhash'\nimport {\n loadMeta,\n saveMeta,\n getOrAddCdnIndex,\n getMetaEntry,\n setMetaEntry,\n} from './utils'\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 + blurhash\n */\nasync function processRemoteImage(url: string): 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 const metadata = await sharp(buffer).metadata()\n \n // Generate blurhash\n const { data, info } = await sharp(buffer)\n .resize(32, 32, { fit: 'inside' })\n .ensureAlpha()\n .raw()\n .toBuffer({ resolveWithObject: true })\n \n const blurhash = encode(new Uint8ClampedArray(data), info.width, info.height, 4, 4)\n \n return {\n o: { w: metadata.width || 0, h: metadata.height || 0 },\n b: blurhash,\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 controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\\n\\n`))\n }\n\n try {\n const { urls } = await request.json() as { urls: string[] }\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 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 const url = urls[i].trim()\n if (!url) continue\n \n sendEvent({\n type: 'progress',\n current: i + 1,\n total,\n percent: Math.round(((i + 1) / total) * 100),\n currentFile: url,\n })\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 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 added.push(path)\n } catch (error) {\n console.error(`Failed to import ${url}:`, error)\n errors.push(url)\n }\n }\n\n await saveMeta(meta)\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 error: 'Source file must be named favicon.png or favicon.jpg' \n }, { status: 400 })\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\n let metadata\n try {\n metadata = await sharp(sourcePath).metadata()\n } catch {\n return jsonResponse({ error: 'Source file is not a valid image' }, { status: 400 })\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 error: 'Output directory src/app/ not found' \n }, { status: 500 })\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 sendEvent({ \n type: 'progress', \n current: i + 1, \n total, \n percent: Math.round(((i + 1) / total) * 100),\n message: `Generating ${config.name} (${config.size}x${config.size})...`\n })\n\n try {\n const outputPath = path.join(outputDir, config.name)\n \n await sharp(sourcePath)\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 } catch (error) {\n console.error(`Failed to generate ${config.name}:`, error)\n errors.push(config.name)\n }\n }\n\n // Build completion message\n let message = `Generated ${generated.length} favicon${generated.length !== 1 ? 's' : ''} 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"],"mappings":";;;AAAA,OAAO,aAAoC;AAC3C,SAAS,SAAS,YAAY;AAC9B,SAAS,qBAAqB;AAC9B,SAAS,eAAe;AACxB,SAAS,YAAY,oBAAoB;AACzC,SAAS,UAAU,eAAe;AAClC,SAAS,oBAAoB;;;ACN7B,SAAS,YAAYA,WAAU;AAC/B,OAAOC,WAAU;;;ACDjB,SAAS,YAAY,UAAU;;;ACA/B,OAAO,UAAU;AAEjB,IAAI,gBAA+B;AAO5B,SAAS,eAAuB;AACrC,MAAI,kBAAkB,MAAM;AAC1B,oBAAgB,QAAQ,IAAI,oBAAoB,QAAQ,IAAI;AAAA,EAC9D;AACA,SAAO;AACT;AAKO,SAAS,iBAAiB,UAA4B;AAC3D,SAAO,KAAK,KAAK,aAAa,GAAG,UAAU,GAAG,QAAQ;AACxD;AAKO,SAAS,eAAe,UAA4B;AACzD,SAAO,KAAK,KAAK,aAAa,GAAG,SAAS,GAAG,QAAQ;AACvD;AAKO,SAAS,iBAAiB,UAA4B;AAC3D,SAAO,KAAK,KAAK,aAAa,GAAG,OAAO,OAAO,GAAG,QAAQ;AAC5D;AAKO,SAAS,oBAAoB,UAA4B;AAC9D,SAAO,KAAK,KAAK,aAAa,GAAG,GAAG,QAAQ;AAC9C;;;ADrCA,eAAsB,WAA8B;AAClD,QAAM,WAAW,YAAY,cAAc;AAE3C,MAAI;AACF,UAAM,UAAU,MAAM,GAAG,SAAS,UAAU,OAAO;AACnD,WAAO,KAAK,MAAM,OAAO;AAAA,EAC3B,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,eAAsB,SAAS,MAA+B;AAC5D,QAAM,UAAU,YAAY;AAC5B,QAAM,GAAG,MAAM,SAAS,EAAE,WAAW,KAAK,CAAC;AAC3C,QAAM,WAAW,YAAY,cAAc;AAG3C,QAAM,UAAoB,CAAC;AAC3B,MAAI,KAAK,OAAO;AACd,YAAQ,QAAQ,KAAK;AAAA,EACvB;AAEA,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,GAAG;AAC/C,QAAI,QAAQ,SAAS;AACnB,cAAQ,GAAG,IAAI;AAAA,IACjB;AAAA,EACF;AAEA,QAAM,GAAG,UAAU,UAAU,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AAC/D;AAKO,SAAS,WAAW,MAA0B;AACnD,SAAO,KAAK,SAAS,CAAC;AACxB;AAYO,SAAS,iBAAiB,MAAgB,QAAwB;AACvE,MAAI,CAAC,KAAK,OAAO;AACf,SAAK,QAAQ,CAAC;AAAA,EAChB;AAGA,QAAM,gBAAgB,OAAO,QAAQ,OAAO,EAAE;AAE9C,QAAM,gBAAgB,KAAK,MAAM,QAAQ,aAAa;AACtD,MAAI,iBAAiB,GAAG;AACtB,WAAO;AAAA,EACT;AAGA,OAAK,MAAM,KAAK,aAAa;AAC7B,SAAO,KAAK,MAAM,SAAS;AAC7B;AAKO,SAAS,aAAa,MAAgB,KAAoC;AAC/E,MAAI,IAAI,WAAW,GAAG,EAAG,QAAO;AAChC,QAAM,QAAQ,KAAK,GAAG;AACtB,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO;AACjC,SAAO;AACT;AAKO,SAAS,aAAa,MAAgB,KAAa,OAAwB;AAChF,OAAK,GAAG,IAAI;AACd;AAYO,SAAS,eAAe,MAA4C;AACzE,SAAO,OAAO,QAAQ,IAAI,EAAE;AAAA,IAC1B,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,IAAI,WAAW,GAAG,KAAK,CAAC,MAAM,QAAQ,KAAK;AAAA,EAChE;AACF;;;AErGA,OAAOC,WAAU;AAEV,SAAS,YAAY,UAA2B;AACrD,QAAM,MAAMA,MAAK,QAAQ,QAAQ,EAAE,YAAY;AAC/C,SAAO,CAAC,QAAQ,SAAS,QAAQ,QAAQ,SAAS,QAAQ,QAAQ,QAAQ,SAAS,MAAM,EAAE,SAAS,GAAG;AACzG;AAEO,SAAS,YAAY,UAA2B;AACrD,QAAM,MAAMA,MAAK,QAAQ,QAAQ,EAAE,YAAY;AAE/C,MAAI,CAAC,QAAQ,SAAS,QAAQ,QAAQ,SAAS,QAAQ,QAAQ,QAAQ,SAAS,MAAM,EAAE,SAAS,GAAG,EAAG,QAAO;AAE9G,MAAI,CAAC,QAAQ,SAAS,QAAQ,QAAQ,QAAQ,MAAM,EAAE,SAAS,GAAG,EAAG,QAAO;AAE5E,MAAI,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,SAAS,MAAM,EAAE,SAAS,GAAG,EAAG,QAAO;AAE5E,MAAI,CAAC,QAAQ,OAAO,EAAE,SAAS,GAAG,EAAG,QAAO;AAC5C,SAAO;AACT;AAEO,SAAS,eAAe,UAA0B;AACvD,QAAM,MAAMA,MAAK,QAAQ,QAAQ,EAAE,YAAY;AAC/C,UAAQ,KAAK;AAAA,IACX,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;;;ACtCA,SAAS,YAAYC,WAAU;AAC/B,OAAOC,WAAU;AACjB,OAAO,WAAW;AAClB,SAAS,cAAc;AAIhB,IAAM,iBAAiB;AAEvB,IAAM,gBAA4F;AAAA,EACvG,OAAO,EAAE,OAAO,KAAK,QAAQ,OAAO,KAAK,KAAK;AAAA,EAC9C,QAAQ,EAAE,OAAO,KAAK,QAAQ,OAAO,KAAK,KAAK;AAAA,EAC/C,OAAO,EAAE,OAAO,MAAM,QAAQ,OAAO,KAAK,KAAK;AACjD;AAEA,eAAsB,aACpB,QACA,UACoB;AACpB,QAAM,gBAAgB,MAAM,MAAM;AAClC,QAAM,WAAW,MAAM,cAAc,SAAS;AAC9C,QAAM,gBAAgB,SAAS,SAAS;AACxC,QAAM,iBAAiB,SAAS,UAAU;AAC1C,QAAM,QAAQ,iBAAiB;AAG/B,QAAM,kBAAkB,SAAS,WAAW,GAAG,IAAI,SAAS,MAAM,CAAC,IAAI;AACvE,QAAM,WAAWC,MAAK,SAAS,iBAAiBA,MAAK,QAAQ,eAAe,CAAC;AAC7E,QAAM,MAAMA,MAAK,QAAQ,eAAe,EAAE,YAAY;AACtD,QAAM,WAAWA,MAAK,QAAQ,eAAe;AAE7C,QAAM,aAAa,cAAc,UAAU,aAAa,MAAM,KAAK,QAAQ;AAC3E,QAAMC,IAAG,MAAM,YAAY,EAAE,WAAW,KAAK,CAAC;AAE9C,QAAM,QAAQ,QAAQ;AACtB,QAAM,YAAY,QAAQ,SAAS;AAGnC,QAAM,QAAmB;AAAA,IACvB,GAAG,EAAE,GAAG,eAAe,GAAG,eAAe;AAAA,EAC3C;AAGA,QAAM,eAAe,aAAa,MAAM,GAAG,QAAQ,GAAG,SAAS,KAAK,GAAG,QAAQ,IAAI,QAAQ,GAAG,SAAS;AACvG,QAAM,WAAW,cAAc,UAAU,YAAY;AAErD,MAAI,YAAY;AAChB,MAAI,aAAa;AAEjB,MAAI,gBAAgB,gBAAgB;AAClC,gBAAY;AACZ,iBAAa,KAAK,MAAM,iBAAiB,KAAK;AAC9C,QAAI,OAAO;AACT,YAAM,MAAM,MAAM,EAAE,OAAO,WAAW,UAAU,EAAE,IAAI,EAAE,SAAS,GAAG,CAAC,EAAE,OAAO,QAAQ;AAAA,IACxF,OAAO;AACL,YAAM,MAAM,MAAM,EAAE,OAAO,WAAW,UAAU,EAAE,KAAK,EAAE,SAAS,GAAG,CAAC,EAAE,OAAO,QAAQ;AAAA,IACzF;AAAA,EACF,OAAO;AACL,QAAI,OAAO;AACT,YAAM,MAAM,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,CAAC,EAAE,OAAO,QAAQ;AAAA,IAC1D,OAAO;AACL,YAAM,MAAM,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,CAAC,EAAE,OAAO,QAAQ;AAAA,IAC3D;AAAA,EACF;AACA,QAAM,IAAI,EAAE,GAAG,WAAW,GAAG,WAAW;AAGxC,aAAW,CAAC,EAAE,UAAU,KAAK,OAAO,QAAQ,aAAa,GAAG;AAC1D,UAAM,EAAE,OAAO,UAAU,QAAQ,IAAI,IAAI;AACzC,QAAI,iBAAiB,UAAU;AAC7B;AAAA,IACF;AAEA,UAAM,YAAY,KAAK,MAAM,WAAW,KAAK;AAC7C,UAAM,eAAe,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS;AACrD,UAAM,eAAe,aAAa,MAAM,eAAe,GAAG,QAAQ,IAAI,YAAY;AAClF,UAAM,WAAW,cAAc,UAAU,YAAY;AAErD,QAAI,OAAO;AACT,YAAM,MAAM,MAAM,EAAE,OAAO,UAAU,SAAS,EAAE,IAAI,EAAE,SAAS,GAAG,CAAC,EAAE,OAAO,QAAQ;AAAA,IACtF,OAAO;AACL,YAAM,MAAM,MAAM,EAAE,OAAO,UAAU,SAAS,EAAE,KAAK,EAAE,SAAS,GAAG,CAAC,EAAE,OAAO,QAAQ;AAAA,IACvF;AAEA,UAAM,GAAG,IAAI,EAAE,GAAG,UAAU,GAAG,UAAU;AAAA,EAC3C;AAGA,QAAM,EAAE,MAAM,KAAK,IAAI,MAAM,MAAM,MAAM,EACtC,OAAO,IAAI,IAAI,EAAE,KAAK,SAAS,CAAC,EAChC,YAAY,EACZ,IAAI,EACJ,SAAS,EAAE,mBAAmB,KAAK,CAAC;AAEvC,QAAM,IAAI,OAAO,IAAI,kBAAkB,IAAI,GAAG,KAAK,OAAO,KAAK,QAAQ,GAAG,CAAC;AAE3E,SAAO;AACT;;;ACjGA,SAAS,YAAYC,WAAU;AAC/B,SAAS,UAAU,kBAAkB,kBAAkB,2BAA2B;;;AC2F3E,SAAS,iBAAiB,cAAsB,MAA2C;AAChG,MAAI,SAAS,QAAQ;AACnB,UAAMC,OAAM,aAAa,MAAM,QAAQ,IAAI,CAAC,KAAK;AACjD,UAAMC,QAAO,aAAa,QAAQ,UAAU,EAAE;AAC9C,UAAMC,aAAYF,KAAI,YAAY,MAAM,SAAS,SAAS;AAC1D,WAAO,UAAUC,KAAI,GAAGC,UAAS;AAAA,EACnC;AACA,QAAM,MAAM,aAAa,MAAM,QAAQ,IAAI,CAAC,KAAK;AACjD,QAAM,OAAO,aAAa,QAAQ,UAAU,EAAE;AAC9C,QAAM,YAAY,IAAI,YAAY,MAAM,SAAS,SAAS;AAC1D,SAAO,UAAU,IAAI,IAAI,IAAI,GAAG,SAAS;AAC3C;AAKO,SAAS,qBAAqB,cAAgC;AACnE,SAAO;AAAA,IACL,iBAAiB,cAAc,MAAM;AAAA,IACrC,iBAAiB,cAAc,IAAI;AAAA,IACnC,iBAAiB,cAAc,IAAI;AAAA,IACnC,iBAAiB,cAAc,IAAI;AAAA,EACrC;AACF;AAKO,SAAS,YAAY,OAAuC;AACjE,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,CAAC,EAAE,MAAM,KAAK,MAAM,MAAM,MAAM,MAAM,MAAM;AACrD;;;ADrHA,SAAS,cAAc;AACrB,QAAM,YAAY,QAAQ,IAAI;AAC9B,QAAM,cAAc,QAAQ,IAAI;AAChC,QAAM,kBAAkB,QAAQ,IAAI;AAEpC,MAAI,CAAC,aAAa,CAAC,eAAe,CAAC,iBAAiB;AAClD,UAAM,IAAI,MAAM,mBAAmB;AAAA,EACrC;AAEA,SAAO,IAAI,SAAS;AAAA,IAClB,QAAQ;AAAA,IACR,UAAU,WAAW,SAAS;AAAA,IAC9B,aAAa,EAAE,aAAa,gBAAgB;AAAA,EAC9C,CAAC;AACH;AAEA,eAAsB,gBAAgB,cAAuC;AAC3E,QAAM,aAAa,QAAQ,IAAI;AAC/B,MAAI,CAAC,WAAY,OAAM,IAAI,MAAM,0BAA0B;AAE3D,QAAM,KAAK,YAAY;AACvB,QAAM,aAAa;AACnB,MAAI;AAEJ,WAAS,UAAU,GAAG,UAAU,YAAY,WAAW;AACrD,QAAI;AACF,YAAM,WAAW,MAAM,GAAG;AAAA,QACxB,IAAI,iBAAiB;AAAA,UACnB,QAAQ;AAAA,UACR,KAAK,aAAa,QAAQ,OAAO,EAAE;AAAA,QACrC,CAAC;AAAA,MACH;AAEA,YAAM,SAAS,SAAS;AACxB,YAAM,SAAmB,CAAC;AAC1B,uBAAiB,SAAS,QAAQ;AAChC,eAAO,KAAK,OAAO,KAAK,KAAK,CAAC;AAAA,MAChC;AACA,aAAO,OAAO,OAAO,MAAM;AAAA,IAC7B,SAAS,OAAO;AACd,kBAAY;AAEZ,UAAI,UAAU,aAAa,GAAG;AAC5B,cAAM,IAAI,QAAQ,CAAAC,aAAW,WAAWA,UAAS,OAAO,UAAU,EAAE,CAAC;AAAA,MACvE;AAAA,IACF;AAAA,EACF;AAEA,QAAM,aAAa,IAAI,MAAM,sBAAsB,YAAY,UAAU,UAAU,WAAW;AAChG;AAEA,eAAsB,YAAY,UAAiC;AACjE,QAAM,aAAa,QAAQ,IAAI;AAC/B,MAAI,CAAC,WAAY,OAAM,IAAI,MAAM,0BAA0B;AAE3D,QAAM,KAAK,YAAY;AAGvB,aAAW,aAAa,qBAAqB,QAAQ,GAAG;AACtD,UAAM,YAAY,cAAc,SAAS;AACzC,QAAI;AACF,YAAM,aAAa,MAAMC,IAAG,SAAS,SAAS;AAC9C,YAAM,GAAG;AAAA,QACP,IAAI,iBAAiB;AAAA,UACnB,QAAQ;AAAA,UACR,KAAK,UAAU,QAAQ,OAAO,EAAE;AAAA,UAChC,MAAM;AAAA,UACN,aAAa,eAAe,SAAS;AAAA,QACvC,CAAC;AAAA,MACH;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAEA,eAAsB,sBAAsB,UAAiC;AAC3E,aAAW,aAAa,qBAAqB,QAAQ,GAAG;AACtD,UAAM,YAAY,cAAc,SAAS;AACzC,QAAI;AACF,YAAMA,IAAG,OAAO,SAAS;AAAA,IAC3B,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAKA,eAAsB,sBAAsB,KAA8B;AACxE,QAAM,aAAa;AACnB,MAAI;AAEJ,WAAS,UAAU,GAAG,UAAU,YAAY,WAAW;AACrD,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,GAAG;AAChC,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,IAAI,MAAM,2BAA2B,GAAG,KAAK,SAAS,MAAM,EAAE;AAAA,MACtE;AACA,YAAM,cAAc,MAAM,SAAS,YAAY;AAC/C,aAAO,OAAO,KAAK,WAAW;AAAA,IAChC,SAAS,OAAO;AACd,kBAAY;AAEZ,UAAI,UAAU,aAAa,GAAG;AAC5B,cAAM,IAAI,QAAQ,CAAAD,aAAW,WAAWA,UAAS,OAAO,UAAU,EAAE,CAAC;AAAA,MACvE;AAAA,IACF;AAAA,EACF;AAEA,QAAM,aAAa,IAAI,MAAM,2BAA2B,GAAG,UAAU,UAAU,WAAW;AAC5F;AAKA,eAAsB,oBAAoB,UAAiC;AACzE,QAAM,aAAa,QAAQ,IAAI;AAC/B,MAAI,CAAC,WAAY,OAAM,IAAI,MAAM,0BAA0B;AAE3D,QAAM,KAAK,YAAY;AACvB,QAAM,YAAY,cAAc,QAAQ;AACxC,QAAM,aAAa,MAAMC,IAAG,SAAS,SAAS;AAE9C,QAAM,GAAG;AAAA,IACP,IAAI,iBAAiB;AAAA,MACnB,QAAQ;AAAA,MACR,KAAK,SAAS,QAAQ,OAAO,EAAE;AAAA,MAC/B,MAAM;AAAA,MACN,aAAa,eAAe,QAAQ;AAAA,IACtC,CAAC;AAAA,EACH;AACF;AAKA,eAAsB,cAAc,UAAkB,eAAuC;AAC3F,QAAM,aAAa,QAAQ,IAAI;AAC/B,MAAI,CAAC,WAAY,OAAM,IAAI,MAAM,0BAA0B;AAE3D,QAAM,KAAK,YAAY;AAGvB,MAAI;AACF,UAAM,GAAG;AAAA,MACP,IAAI,oBAAoB;AAAA,QACtB,QAAQ;AAAA,QACR,KAAK,SAAS,QAAQ,OAAO,EAAE;AAAA,MACjC,CAAC;AAAA,IACH;AAAA,EACF,QAAQ;AAAA,EAER;AAGA,MAAI,eAAe;AACjB,eAAW,aAAa,qBAAqB,QAAQ,GAAG;AACtD,UAAI;AACF,cAAM,GAAG;AAAA,UACP,IAAI,oBAAoB;AAAA,YACtB,QAAQ;AAAA,YACR,KAAK,UAAU,QAAQ,OAAO,EAAE;AAAA,UAClC,CAAC;AAAA,QACH;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACF;AAKA,eAAsB,wBAAwB,UAAiC;AAC7E,QAAM,aAAa,QAAQ,IAAI;AAC/B,MAAI,CAAC,WAAY,OAAM,IAAI,MAAM,0BAA0B;AAE3D,QAAM,KAAK,YAAY;AAEvB,aAAW,aAAa,qBAAqB,QAAQ,GAAG;AACtD,QAAI;AACF,YAAM,GAAG;AAAA,QACP,IAAI,oBAAoB;AAAA,UACtB,QAAQ;AAAA,UACR,KAAK,UAAU,QAAQ,OAAO,EAAE;AAAA,QAClC,CAAC;AAAA,MACH;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AACF;;;AEhMO,SAAS,aACd,MACA,MACU;AACV,QAAM,UAAU,IAAI,QAAQ;AAAA,IAC1B,gBAAgB;AAAA,IAChB,GAAG,MAAM;AAAA,EACX,CAAC;AAED,SAAO,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG;AAAA,IACxC,QAAQ,MAAM,UAAU;AAAA,IACxB;AAAA,EACF,CAAC;AACH;;;APTA,SAAS,sBAAsB,cAAsB,OAA2E;AAC9H,QAAM,aAAsE,CAAC;AAE7E,MAAI,MAAM,GAAG;AACX,eAAW,KAAK,EAAE,MAAM,iBAAiB,cAAc,MAAM,GAAG,MAAM,IAAI,CAAC;AAAA,EAC7E;AACA,MAAI,MAAM,IAAI;AACZ,eAAW,KAAK,EAAE,MAAM,iBAAiB,cAAc,IAAI,GAAG,MAAM,KAAK,CAAC;AAAA,EAC5E;AACA,MAAI,MAAM,IAAI;AACZ,eAAW,KAAK,EAAE,MAAM,iBAAiB,cAAc,IAAI,GAAG,MAAM,KAAK,CAAC;AAAA,EAC5E;AACA,MAAI,MAAM,IAAI;AACZ,eAAW,KAAK,EAAE,MAAM,iBAAiB,cAAc,IAAI,GAAG,MAAM,KAAK,CAAC;AAAA,EAC5E;AAEA,SAAO;AACT;AAKA,SAAS,eACP,cACA,aACA,SACA,aACsF;AACtF,MAAI,aAAa;AACjB,MAAI,cAAc;AAClB,MAAI,aAAa;AACjB,MAAI,cAAc;AAElB,aAAW,CAAC,KAAK,KAAK,KAAK,aAAa;AACtC,QAAI,IAAI,WAAW,YAAY,GAAG;AAChC,UAAI,MAAM,MAAM,QAAW;AAEzB,cAAM,SAAS,QAAQ,MAAM,CAAC,GAAG,QAAQ,OAAO,EAAE,KAAK;AACvD,YAAI,WAAW,aAAa;AAC1B;AAAA,QACF,OAAO;AACL;AAAA,QACF;AAAA,MACF,OAAO;AACL;AAAA,MACF;AAEA,UAAI,MAAM,MAAM,GAAG;AACjB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,YAAY,aAAa,YAAY,YAAY;AAC5D;AAMA,eAAsB,WAAW,SAAkB;AACjD,QAAM,eAAe,IAAI,IAAI,QAAQ,GAAG,EAAE;AAC1C,QAAM,gBAAgB,aAAa,IAAI,MAAM,KAAK;AAElD,MAAI;AACF,UAAM,OAAO,MAAM,SAAS;AAC5B,UAAM,cAAc,eAAe,IAAI;AACvC,UAAM,UAAU,WAAW,IAAI;AAC/B,UAAM,cAAc,QAAQ,IAAI,0BAA0B,QAAQ,OAAO,EAAE,KAAK;AAKhF,UAAM,eAAe,cAAc,QAAQ,cAAc,EAAE;AAC3D,UAAM,aAAa,eAAe,IAAI,YAAY,MAAM;AAExD,UAAM,QAAoB,CAAC;AAC3B,UAAM,cAAc,oBAAI,IAAY;AACpC,UAAM,WAAW,YAAY,IAAI,CAAC,CAAC,GAAG,MAAM,GAAG;AAG/C,UAAM,uBAAuB,iBAAiB,YAAY,aAAa,WAAW,SAAS;AAG3F,QAAI,sBAAsB;AAExB,YAAM,gBAAgB,aAAa,QAAQ,cAAc,EAAE;AAC3D,YAAM,eAAe,gBAAgB,IAAI,aAAa,MAAM;AAG5D,YAAM,gBAA8F,CAAC;AAErG,iBAAW,CAAC,KAAK,KAAK,KAAK,aAAa;AACtC,YAAI,YAAY,KAAK,GAAG;AACtB,gBAAM,aAAa,sBAAsB,KAAK,KAAK;AACnD,qBAAW,SAAS,YAAY;AAC9B,0BAAc,KAAK,EAAE,GAAG,OAAO,aAAa,IAAI,CAAC;AAAA,UACnD;AAAA,QACF;AAAA,MACF;AAGA,iBAAW,SAAS,eAAe;AAGjC,cAAM,gBAAgB,MAAM,KAAK,QAAQ,gBAAgB,EAAE;AAG3D,cAAM,gBAAgB,YAAY,KAAK,CAAC,CAAC,CAAC,MAAM,MAAM,MAAM,WAAW,IAAI,CAAC;AAC5E,cAAM,WAAW,eAAe;AAChC,cAAM,aAAa,aAAa,SAAY,QAAQ,QAAQ,IAAI;AAEhE,cAAM,eAAe,aAAa,GAAG,UAAU,GAAG,MAAM,IAAI,KAAK,MAAM;AAEvE,cAAM,kBAAkB,aAAa;AACrC,cAAM,uBAAuB,YAAY,QAAQ,OAAO,EAAE,KAAK;AAC/D,cAAM,WAAW,mBAAmB,yBAAyB;AAG7D,cAAM,YAAY,gBAAgB,MAAM,IAAI;AAC5C,cAAM,aAAa,YAAY,EAAE,OAAO,UAAU,GAAG,QAAQ,UAAU,EAAE,IAAI;AAG7E,YAAI,kBAAkB,IAAI;AAExB,gBAAM,aAAa,cAAc,QAAQ,GAAG;AAC5C,cAAI,eAAe,IAAI;AAErB,kBAAM,WAAW;AACjB,kBAAM,KAAK;AAAA,cACT,MAAM;AAAA,cACN,MAAM,iBAAiB,QAAQ;AAAA,cAC/B,MAAM;AAAA,cACN,WAAW;AAAA,cACX,cAAc;AAAA,cACd,aAAa;AAAA,cACb,WAAW;AAAA,cACX;AAAA,cACA;AAAA,cACA;AAAA,YACF,CAAC;AAAA,UACH,OAAO;AAEL,kBAAM,aAAa,cAAc,MAAM,GAAG,UAAU;AACpD,gBAAI,CAAC,YAAY,IAAI,UAAU,GAAG;AAChC,0BAAY,IAAI,UAAU;AAE1B,oBAAM,eAAe,IAAI,UAAU;AACnC,oBAAM,eAAe,cAAc;AAAA,gBAAO,OACxC,EAAE,KAAK,QAAQ,aAAa,EAAE,EAAE,WAAW,YAAY;AAAA,cACzD;AACA,kBAAI,mBAAmB;AACvB,kBAAI,oBAAoB;AACxB,kBAAI,mBAAmB;AACvB,yBAAW,MAAM,cAAc;AAC7B,sBAAM,YAAY,KAAK,GAAG,WAAW;AACrC,oBAAI,WAAW,MAAM,QAAW;AAC9B,wBAAM,cAAc,QAAQ,UAAU,CAAC,GAAG,QAAQ,QAAQ,EAAE;AAC5D,sBAAI,eAAe,gBAAgB,aAAa;AAC9C;AAAA,kBACF,OAAO;AACL;AAAA,kBACF;AAAA,gBACF,OAAO;AACL;AAAA,gBACF;AAAA,cACF;AACA,oBAAM,KAAK;AAAA,gBACT,MAAM;AAAA,gBACN,MAAM,iBAAiB,UAAU;AAAA,gBACjC,MAAM;AAAA,gBACN,WAAW,aAAa;AAAA,gBACxB,YAAY;AAAA,gBACZ,aAAa;AAAA,gBACb,YAAY;AAAA,gBACZ,aAAa;AAAA,cACf,CAAC;AAAA,YACH;AAAA,UACF;AAAA,QACF,OAAO;AAEL,cAAI,CAAC,cAAc,WAAW,gBAAgB,GAAG,KAAK,kBAAkB,cAAe;AAEvF,gBAAM,YAAY,cAAc,MAAM,cAAc,SAAS,CAAC;AAC9D,cAAI,CAAC,UAAW;AAEhB,gBAAM,aAAa,UAAU,QAAQ,GAAG;AACxC,cAAI,eAAe,IAAI;AAErB,kBAAM,KAAK;AAAA,cACT,MAAM;AAAA,cACN,MAAM,iBAAiB,aAAa,IAAI,SAAS;AAAA,cACjD,MAAM;AAAA,cACN,WAAW;AAAA,cACX,cAAc;AAAA,cACd,aAAa;AAAA,cACb,WAAW;AAAA,cACX;AAAA,cACA;AAAA,cACA;AAAA,YACF,CAAC;AAAA,UACH,OAAO;AAEL,kBAAM,aAAa,UAAU,MAAM,GAAG,UAAU;AAChD,gBAAI,CAAC,YAAY,IAAI,UAAU,GAAG;AAChC,0BAAY,IAAI,UAAU;AAC1B,oBAAM,eAAe,GAAG,aAAa,IAAI,UAAU;AACnD,oBAAM,eAAe,cAAc;AAAA,gBAAO,OACxC,EAAE,KAAK,QAAQ,eAAe,EAAE,EAAE,WAAW,YAAY;AAAA,cAC3D;AACA,kBAAI,gBAAgB;AACpB,kBAAI,iBAAiB;AACrB,kBAAI,gBAAgB;AACpB,yBAAW,MAAM,cAAc;AAC7B,sBAAM,YAAY,KAAK,GAAG,WAAW;AACrC,oBAAI,WAAW,MAAM,QAAW;AAC9B,wBAAM,cAAc,QAAQ,UAAU,CAAC,GAAG,QAAQ,QAAQ,EAAE;AAC5D,sBAAI,eAAe,gBAAgB,aAAa;AAC9C;AAAA,kBACF,OAAO;AACL;AAAA,kBACF;AAAA,gBACF,OAAO;AACL;AAAA,gBACF;AAAA,cACF;AACA,oBAAM,KAAK;AAAA,gBACT,MAAM;AAAA,gBACN,MAAM,iBAAiB,aAAa,IAAI,UAAU;AAAA,gBAClD,MAAM;AAAA,gBACN,WAAW,aAAa;AAAA,gBACxB,YAAY;AAAA,gBACZ,aAAa;AAAA,gBACb,YAAY;AAAA,gBACZ,aAAa;AAAA,cACf,CAAC;AAAA,YACH;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,aAAO,aAAa,EAAE,MAAM,CAAC;AAAA,IAC/B;AAGA,UAAM,cAAc,iBAAiB,aAAa;AAClD,QAAI;AACF,YAAM,aAAa,MAAMC,IAAG,QAAQ,aAAa,EAAE,eAAe,KAAK,CAAC;AACxE,iBAAW,SAAS,YAAY;AAC9B,YAAI,MAAM,KAAK,WAAW,GAAG,EAAG;AAEhC,YAAI,MAAM,YAAY,GAAG;AACvB,cAAI,CAAC,YAAY,IAAI,MAAM,IAAI,GAAG;AAChC,wBAAY,IAAI,MAAM,IAAI;AAG1B,kBAAM,iBAAiB,MAAM,SAAS,YAAY,CAAC;AACnD,kBAAM,aAAa,eAAe,UAAU,YAAY,IAAI,MAAM,IAAI,KAAK,UAAU,MAAM,IAAI;AAG/F,gBAAI,YAAY;AAChB,gBAAI,aAAa;AACjB,gBAAI,cAAc;AAClB,gBAAI,aAAa;AACjB,gBAAI,cAAc;AAElB,gBAAI,gBAAgB;AAElB,yBAAW,CAAC,KAAK,SAAS,KAAK,aAAa;AAC1C,oBAAI,YAAY,SAAS,GAAG;AAC1B,wBAAM,aAAa,sBAAsB,KAAK,SAAS,EAAE;AACzD,+BAAa;AAEb,sBAAI,UAAU,MAAM,QAAW;AAC7B,0BAAM,cAAc,QAAQ,UAAU,CAAC,GAAG,QAAQ,QAAQ,EAAE;AAC5D,wBAAI,eAAe,gBAAgB,aAAa;AAC9C,oCAAc;AAAA,oBAChB,OAAO;AACL,qCAAe;AAAA,oBACjB;AAAA,kBACF,OAAO;AACL,kCAAc;AAAA,kBAChB;AAAA,gBACF;AAAA,cACF;AAAA,YACF,OAAO;AAEL,oBAAM,eAAe,eAAe,MAAM,IAAI,MAAM,IAAI,MAAM,GAAG,UAAU,GAAG,MAAM,IAAI;AACxF,yBAAW,KAAK,UAAU;AACxB,oBAAI,EAAE,WAAW,YAAY,EAAG;AAAA,cAClC;AAEA,oBAAM,SAAS,eAAe,cAAc,aAAa,SAAS,WAAW;AAC7E,2BAAa,OAAO;AACpB,4BAAc,OAAO;AACrB,2BAAa,OAAO;AACpB,4BAAc,OAAO;AAAA,YACvB;AAEA,kBAAM,KAAK;AAAA,cACT,MAAM,MAAM;AAAA,cACZ,MAAM;AAAA,cACN,MAAM;AAAA,cACN;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA,aAAa;AAAA,YACf,CAAC;AAAA,UACH;AAAA,QACF;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAGA,QAAI,CAAC,gBAAgB,CAAC,YAAY,IAAI,QAAQ,GAAG;AAC/C,UAAI,iBAAiB;AACrB,UAAI,gBAAgB;AACpB,UAAI,iBAAiB;AACrB,UAAI,gBAAgB;AACpB,iBAAW,CAAC,KAAK,KAAK,KAAK,aAAa;AACtC,YAAI,YAAY,KAAK,GAAG;AACtB,gBAAM,aAAa,sBAAsB,KAAK,KAAK,EAAE;AACrD,4BAAkB;AAElB,cAAI,MAAM,MAAM,QAAW;AACzB,kBAAM,cAAc,QAAQ,MAAM,CAAC,GAAG,QAAQ,QAAQ,EAAE;AACxD,gBAAI,eAAe,gBAAgB,aAAa;AAC9C,+BAAiB;AAAA,YACnB,OAAO;AACL,gCAAkB;AAAA,YACpB;AAAA,UACF,OAAO;AACL,6BAAiB;AAAA,UACnB;AAAA,QACF;AAAA,MACF;AACA,UAAI,iBAAiB,GAAG;AACtB,cAAM,KAAK;AAAA,UACT,MAAM;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,UACN,WAAW;AAAA,UACX,YAAY;AAAA,UACZ,aAAa;AAAA,UACb,YAAY;AAAA,UACZ,aAAa;AAAA,QACf,CAAC;AAAA,MACH;AAAA,IACF;AAGA,QAAI,YAAY,WAAW,KAAK,MAAM,WAAW,GAAG;AAClD,aAAO,aAAa,EAAE,OAAO,CAAC,GAAG,SAAS,KAAK,CAAC;AAAA,IAClD;AAEA,eAAW,CAAC,KAAK,KAAK,KAAK,aAAa;AAEtC,UAAI,CAAC,IAAI,WAAW,UAAU,KAAK,eAAe,IAAK;AACvD,UAAI,eAAe,OAAO,CAAC,IAAI,WAAW,GAAG,EAAG;AAGhD,YAAM,YAAY,eAAe,MAAM,IAAI,MAAM,CAAC,IAAI,IAAI,MAAM,WAAW,MAAM;AAGjF,UAAI,CAAC,UAAW;AAGhB,YAAM,aAAa,UAAU,QAAQ,GAAG;AAExC,UAAI,eAAe,IAAI;AAErB,cAAM,aAAa,UAAU,MAAM,GAAG,UAAU;AAEhD,YAAI,CAAC,YAAY,IAAI,UAAU,GAAG;AAChC,sBAAY,IAAI,UAAU;AAG1B,gBAAM,eAAe,eAAe,MAAM,IAAI,UAAU,MAAM,GAAG,UAAU,GAAG,UAAU;AACxF,cAAI,YAAY;AAChB,qBAAW,KAAK,UAAU;AACxB,gBAAI,EAAE,WAAW,YAAY,EAAG;AAAA,UAClC;AAGA,gBAAM,SAAS,eAAe,cAAc,aAAa,SAAS,WAAW;AAE7E,gBAAM,KAAK;AAAA,YACT,MAAM;AAAA,YACN,MAAM,eAAe,UAAU,YAAY,IAAI,UAAU,KAAK,UAAU,UAAU;AAAA,YAClF,MAAM;AAAA,YACN;AAAA,YACA,YAAY,OAAO;AAAA,YACnB,aAAa,OAAO;AAAA,YACpB,YAAY,OAAO;AAAA,YACnB,aAAa,OAAO;AAAA,YACpB,aAAa;AAAA,UACf,CAAC;AAAA,QACH;AAAA,MACF,OAAO;AAEL,cAAM,WAAW;AACjB,cAAM,UAAU,YAAY,QAAQ;AACpC,cAAM,kBAAkB,MAAM,MAAM;AAGpC,cAAM,aAAa,mBAAmB,MAAM,MAAM,SAAY,QAAQ,MAAM,CAAC,IAAI;AACjF,cAAM,uBAAuB,YAAY,QAAQ,OAAO,EAAE,KAAK;AAC/D,cAAM,WAAW,oBAAoB,CAAC,eAAe,yBAAyB;AAE9E,YAAI;AACJ,YAAI,eAAe;AACnB,YAAI;AAEJ,cAAM,mBAAmB,YAAY,KAAK;AAE1C,YAAI,WAAW,kBAAkB;AAE/B,gBAAM,YAAY,iBAAiB,KAAK,IAAI;AAE5C,cAAI,mBAAmB,MAAM,MAAM,QAAW;AAE5C,kBAAM,SAAS,QAAQ,MAAM,CAAC;AAC9B,gBAAI,QAAQ;AACV,0BAAY,GAAG,MAAM,GAAG,SAAS;AACjC,6BAAe;AAAA,YACjB;AAAA,UACF,OAAO;AAEL,kBAAM,iBAAiB,cAAc,SAAS;AAC9C,gBAAI;AACF,oBAAMA,IAAG,OAAO,cAAc;AAC9B,0BAAY;AACZ,6BAAe;AAAA,YACjB,QAAQ;AAEN,0BAAY;AACZ,6BAAe;AAAA,YACjB;AAAA,UACF;AAAA,QACF,WAAW,SAAS;AAElB,cAAI,mBAAmB,MAAM,MAAM,QAAW;AAC5C,kBAAM,SAAS,QAAQ,MAAM,CAAC;AAC9B,wBAAY,SAAS,GAAG,MAAM,GAAG,GAAG,KAAK;AAAA,UAC3C,OAAO;AACL,wBAAY;AAAA,UACd;AACA,yBAAe;AAAA,QACjB;AAGA,YAAI,CAAC,iBAAiB;AACpB,cAAI;AACF,kBAAM,WAAW,cAAc,GAAG;AAClC,kBAAM,QAAQ,MAAMA,IAAG,KAAK,QAAQ;AACpC,uBAAW,MAAM;AAAA,UACnB,QAAQ;AAAA,UAER;AAAA,QACF;AAEA,cAAM,KAAK;AAAA,UACT,MAAM;AAAA,UACN,MAAM,eAAe,UAAU,YAAY,IAAI,QAAQ,KAAK,UAAU,QAAQ;AAAA,UAC9E,MAAM;AAAA,UACN,MAAM;AAAA,UACN;AAAA,UACA;AAAA,UACA,aAAa;AAAA,UACb,WAAW;AAAA,UACX,YAAY;AAAA,UACZ;AAAA,UACA,aAAa;AAAA,UACb,YAAY,MAAM,IAAI,EAAE,OAAO,MAAM,EAAE,GAAG,QAAQ,MAAM,EAAE,EAAE,IAAI;AAAA,UAChE,WAAW,MAAM,MAAM;AAAA,QACzB,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO,aAAa,EAAE,MAAM,CAAC;AAAA,EAC/B,SAAS,OAAO;AACd,YAAQ,MAAM,6BAA6B,KAAK;AAChD,WAAO,aAAa,EAAE,OAAO,2BAA2B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC5E;AACF;AAEA,eAAsB,aAAa,SAAkB;AACnD,QAAM,eAAe,IAAI,IAAI,QAAQ,GAAG,EAAE;AAC1C,QAAM,QAAQ,aAAa,IAAI,GAAG,GAAG,YAAY,KAAK;AAEtD,MAAI,MAAM,SAAS,GAAG;AACpB,WAAO,aAAa,EAAE,OAAO,CAAC,EAAE,CAAC;AAAA,EACnC;AAEA,MAAI;AACF,UAAM,OAAO,MAAM,SAAS;AAC5B,UAAM,cAAc,eAAe,IAAI;AACvC,UAAM,UAAU,WAAW,IAAI;AAC/B,UAAM,cAAc,QAAQ,IAAI,0BAA0B,QAAQ,OAAO,EAAE,KAAK;AAChF,UAAM,QAAoB,CAAC;AAE3B,eAAW,CAAC,KAAK,KAAK,KAAK,aAAa;AAEtC,UAAI,CAAC,IAAI,YAAY,EAAE,SAAS,KAAK,EAAG;AAExC,YAAM,WAAWC,MAAK,SAAS,GAAG;AAClC,YAAM,eAAe,IAAI,MAAM,CAAC;AAChC,YAAM,UAAU,YAAY,QAAQ;AACpC,YAAM,kBAAkB,MAAM,MAAM;AAGpC,YAAM,aAAa,mBAAmB,MAAM,MAAM,SAAY,QAAQ,MAAM,CAAC,IAAI;AACjF,YAAM,uBAAuB,YAAY,QAAQ,OAAO,EAAE,KAAK;AAC/D,YAAM,WAAW,oBAAoB,CAAC,eAAe,yBAAyB;AAE9E,UAAI;AACJ,UAAI,eAAe;AACnB,YAAM,mBAAmB,YAAY,KAAK;AAE1C,UAAI,WAAW,kBAAkB;AAE/B,cAAM,YAAY,iBAAiB,KAAK,IAAI;AAE5C,YAAI,mBAAmB,MAAM,MAAM,QAAW;AAC5C,gBAAM,SAAS,QAAQ,MAAM,CAAC;AAC9B,cAAI,QAAQ;AACV,wBAAY,GAAG,MAAM,GAAG,SAAS;AACjC,2BAAe;AAAA,UACjB;AAAA,QACF,OAAO;AACL,gBAAM,iBAAiB,cAAc,SAAS;AAC9C,cAAI;AACF,kBAAMD,IAAG,OAAO,cAAc;AAC9B,wBAAY;AACZ,2BAAe;AAAA,UACjB,QAAQ;AACN,wBAAY;AACZ,2BAAe;AAAA,UACjB;AAAA,QACF;AAAA,MACF,WAAW,SAAS;AAElB,YAAI,mBAAmB,MAAM,MAAM,QAAW;AAC5C,gBAAM,SAAS,QAAQ,MAAM,CAAC;AAC9B,sBAAY,SAAS,GAAG,MAAM,GAAG,GAAG,KAAK;AAAA,QAC3C,OAAO;AACL,sBAAY;AAAA,QACd;AACA,uBAAe;AAAA,MACjB;AAEA,YAAM,KAAK;AAAA,QACT,MAAM;AAAA,QACN,MAAM,UAAU,YAAY;AAAA,QAC5B,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA,aAAa;AAAA,QACb,WAAW;AAAA,QACX,YAAY;AAAA,QACZ;AAAA,QACA,YAAY,MAAM,IAAI,EAAE,OAAO,MAAM,EAAE,GAAG,QAAQ,MAAM,EAAE,EAAE,IAAI;AAAA,QAChE,WAAW,MAAM,MAAM;AAAA,MACzB,CAAC;AAAA,IACH;AAEA,WAAO,aAAa,EAAE,MAAM,CAAC;AAAA,EAC/B,SAAS,OAAO;AACd,YAAQ,MAAM,qBAAqB,KAAK;AACxC,WAAO,aAAa,EAAE,OAAO,mBAAmB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACpE;AACF;AAEA,eAAsB,oBAAoB;AACxC,MAAI;AACF,UAAM,OAAO,MAAM,SAAS;AAC5B,UAAM,cAAc,eAAe,IAAI;AACvC,UAAM,YAAY,oBAAI,IAAY;AAGlC,eAAW,CAAC,GAAG,KAAK,aAAa;AAC/B,YAAM,QAAQ,IAAI,MAAM,GAAG;AAE3B,UAAI,UAAU;AACd,eAAS,IAAI,GAAG,IAAI,MAAM,SAAS,GAAG,KAAK;AACzC,kBAAU,UAAU,GAAG,OAAO,IAAI,MAAM,CAAC,CAAC,KAAK,MAAM,CAAC;AACtD,kBAAU,IAAI,OAAO;AAAA,MACvB;AAAA,IACF;AAGA,mBAAe,QAAQ,KAAa,cAAqC;AACvE,UAAI;AACF,cAAM,UAAU,MAAMA,IAAG,QAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;AAC7D,mBAAW,SAAS,SAAS;AAC3B,cAAI,MAAM,YAAY,KAAK,CAAC,MAAM,KAAK,WAAW,GAAG,KAAK,MAAM,SAAS,UAAU;AACjF,kBAAM,gBAAgB,eAAe,GAAG,YAAY,IAAI,MAAM,IAAI,KAAK,MAAM;AAC7E,sBAAU,IAAI,aAAa;AAE3B,kBAAM,QAAQC,MAAK,KAAK,KAAK,MAAM,IAAI,GAAG,aAAa;AAAA,UACzD;AAAA,QACF;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,UAAM,YAAY,cAAc;AAChC,UAAM,QAAQ,WAAW,EAAE;AAE3B,UAAM,UAA2D,CAAC;AAClE,YAAQ,KAAK,EAAE,MAAM,UAAU,MAAM,UAAU,OAAO,EAAE,CAAC;AAEzD,UAAM,gBAAgB,MAAM,KAAK,SAAS,EAAE,KAAK;AACjD,eAAW,cAAc,eAAe;AACtC,YAAM,QAAQ,WAAW,MAAM,GAAG,EAAE;AACpC,YAAM,OAAO,WAAW,MAAM,GAAG,EAAE,IAAI,KAAK;AAC5C,cAAQ,KAAK;AAAA,QACX,MAAM,UAAU,UAAU;AAAA,QAC1B;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH;AAEA,WAAO,aAAa,EAAE,QAAQ,CAAC;AAAA,EACjC,SAAS,OAAO;AACd,YAAQ,MAAM,2BAA2B,KAAK;AAC9C,WAAO,aAAa,EAAE,OAAO,yBAAyB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC1E;AACF;AAEA,eAAsB,oBAAoB;AACxC,MAAI;AACF,UAAM,OAAO,MAAM,SAAS;AAC5B,UAAM,cAAc,eAAe,IAAI;AACvC,UAAM,YAAsB,CAAC;AAE7B,eAAW,CAAC,GAAG,KAAK,aAAa;AAC/B,YAAM,WAAWA,MAAK,SAAS,GAAG;AAClC,UAAI,YAAY,QAAQ,GAAG;AACzB,kBAAU,KAAK,IAAI,MAAM,CAAC,CAAC;AAAA,MAC7B;AAAA,IACF;AAEA,WAAO,aAAa;AAAA,MAClB,OAAO,UAAU;AAAA,MACjB,QAAQ;AAAA,IACV,CAAC;AAAA,EACH,SAAS,OAAO;AACd,YAAQ,MAAM,2BAA2B,KAAK;AAC9C,WAAO,aAAa,EAAE,OAAO,yBAAyB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC1E;AACF;AAEA,eAAsB,mBAAmB,SAAkB;AACzD,MAAI;AACF,UAAM,eAAe,IAAI,IAAI,QAAQ,GAAG,EAAE;AAC1C,UAAM,eAAe,aAAa,IAAI,SAAS;AAE/C,QAAI,CAAC,cAAc;AACjB,aAAO,aAAa,EAAE,OAAO,sBAAsB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACvE;AAEA,UAAM,UAAU,aAAa,MAAM,GAAG;AACtC,UAAM,OAAO,MAAM,SAAS;AAC5B,UAAM,cAAc,eAAe,IAAI;AACvC,UAAM,WAAqB,CAAC;AAG5B,UAAM,WAAW,QAAQ,IAAI,OAAK;AAChC,YAAM,MAAM,EAAE,QAAQ,cAAc,EAAE;AACtC,aAAO,MAAM,IAAI,GAAG,MAAM;AAAA,IAC5B,CAAC;AAED,eAAW,CAAC,GAAG,KAAK,aAAa;AAE/B,iBAAW,UAAU,UAAU;AAC7B,YAAI,IAAI,WAAW,MAAM,KAAM,WAAW,OAAO,IAAI,WAAW,GAAG,GAAI;AACrE,mBAAS,KAAK,IAAI,MAAM,CAAC,CAAC;AAC1B;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,WAAO,aAAa;AAAA,MAClB,OAAO,SAAS;AAAA,MAChB,QAAQ;AAAA;AAAA,IACV,CAAC;AAAA,EACH,SAAS,OAAO;AACd,YAAQ,MAAM,+BAA+B,KAAK;AAClD,WAAO,aAAa,EAAE,OAAO,6BAA6B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC9E;AACF;;;AQpsBA,SAAS,YAAYC,WAAU;AAC/B,OAAOC,WAAU;AACjB,OAAOC,YAAW;;;ACFlB,SAAS,YAAYC,WAAU;AAC/B,OAAOC,WAAU;AAQV,SAAS,qBAAqB,UAA2B;AAE9D,MAAI,SAAS,WAAW,GAAG,EAAG,QAAO;AAGrC,QAAM,qBAAqB,CAAC,aAAa,eAAe,eAAe,mBAAmB;AAC1F,MAAI,mBAAmB,SAAS,SAAS,YAAY,CAAC,EAAG,QAAO;AAEhE,SAAO;AACT;AAQA,eAAsB,mBAAmB,YAAmC;AAC1E,QAAM,aAAa,cAAc;AAGjC,QAAM,mBAAmBC,MAAK,QAAQ,UAAU;AAChD,QAAM,mBAAmBA,MAAK,QAAQ,UAAU;AAGhD,MAAI,qBAAqB,kBAAkB;AACzC;AAAA,EACF;AAGA,MAAI,CAAC,iBAAiB,WAAW,gBAAgB,GAAG;AAClD;AAAA,EACF;AAEA,MAAI;AACF,UAAM,UAAU,MAAMC,IAAG,QAAQ,UAAU;AAG3C,UAAM,oBAAoB,QAAQ,OAAO,OAAK,CAAC,qBAAqB,CAAC,CAAC;AAGtE,QAAI,kBAAkB,WAAW,GAAG;AAElC,iBAAW,SAAS,SAAS;AAC3B,YAAI,qBAAqB,KAAK,GAAG;AAC/B,cAAI;AACF,kBAAMA,IAAG,OAAOD,MAAK,KAAK,YAAY,KAAK,CAAC;AAAA,UAC9C,QAAQ;AAAA,UAER;AAAA,QACF;AAAA,MACF;AAGA,YAAMC,IAAG,MAAM,UAAU;AAGzB,YAAM,eAAeD,MAAK,QAAQ,UAAU;AAC5C,YAAM,mBAAmB,YAAY;AAAA,IACvC;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAkBA,eAAsB,6BAA6B,KAA+B;AAChF,MAAI;AACF,UAAM,UAAU,MAAME,IAAG,QAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;AAC7D,QAAI,UAAU;AAEd,eAAW,SAAS,SAAS;AAC3B,UAAI,MAAM,YAAY,GAAG;AACvB,cAAM,cAAc,MAAM,6BAA6BC,MAAK,KAAK,KAAK,MAAM,IAAI,CAAC;AACjF,YAAI,CAAC,YAAa,WAAU;AAAA,MAC9B,WAAW,CAAC,qBAAqB,MAAM,IAAI,GAAG;AAE5C,kBAAU;AAAA,MACZ;AAAA,IACF;AAGA,QAAI,SAAS;AAEX,iBAAW,SAAS,SAAS;AAC3B,YAAI,CAAC,MAAM,YAAY,KAAK,qBAAqB,MAAM,IAAI,GAAG;AAC5D,cAAI;AACF,kBAAMD,IAAG,OAAOC,MAAK,KAAK,KAAK,MAAM,IAAI,CAAC;AAAA,UAC5C,QAAQ;AAAA,UAER;AAAA,QACF;AAAA,MACF;AACA,YAAMD,IAAG,MAAM,GAAG;AAAA,IACpB;AAEA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ADrGA,eAAsB,aAAa,SAAkB;AACnD,MAAI;AACF,UAAM,WAAW,MAAM,QAAQ,SAAS;AACxC,UAAM,OAAO,SAAS,IAAI,MAAM;AAChC,UAAM,aAAa,SAAS,IAAI,MAAM,KAAe;AAErD,QAAI,CAAC,MAAM;AACT,aAAO,aAAa,EAAE,OAAO,mBAAmB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACpE;AAEA,UAAM,QAAQ,MAAM,KAAK,YAAY;AACrC,UAAM,SAAS,OAAO,KAAK,KAAK;AAEhC,UAAM,WAAW,KAAK;AACtB,UAAM,MAAME,MAAK,QAAQ,QAAQ,EAAE,YAAY;AAE/C,UAAM,UAAU,YAAY,QAAQ;AACpC,UAAM,UAAU,YAAY,QAAQ;AAEpC,UAAM,OAAO,MAAM,SAAS;AAE5B,QAAI,cAAc;AAClB,QAAI,eAAe,UAAU;AAC3B,oBAAc;AAAA,IAChB,WAAW,WAAW,WAAW,SAAS,GAAG;AAC3C,oBAAc,WAAW,QAAQ,WAAW,EAAE;AAAA,IAChD;AAEA,QAAI,gBAAgB,YAAY,YAAY,WAAW,SAAS,GAAG;AACjE,aAAO;AAAA,QACL,EAAE,OAAO,uGAAuG;AAAA,QAChH,EAAE,QAAQ,IAAI;AAAA,MAChB;AAAA,IACF;AAGA,QAAI,WAAW,OAAO,cAAc,GAAG,WAAW,IAAI,QAAQ,KAAK;AAGnE,QAAI,KAAK,QAAQ,GAAG;AAClB,YAAM,WAAWA,MAAK,SAAS,UAAU,GAAG;AAC5C,UAAI,UAAU;AACd,UAAI,cAAc,GAAG,QAAQ,IAAI,OAAO,GAAG,GAAG;AAC9C,UAAI,SAAS,OAAO,cAAc,GAAG,WAAW,IAAI,WAAW,KAAK;AAEpE,aAAO,KAAK,MAAM,GAAG;AACnB;AACA,sBAAc,GAAG,QAAQ,IAAI,OAAO,GAAG,GAAG;AAC1C,iBAAS,OAAO,cAAc,GAAG,WAAW,IAAI,WAAW,KAAK;AAAA,MAClE;AAEA,iBAAW;AAAA,IACb;AAGA,UAAM,iBAAiBA,MAAK,SAAS,QAAQ;AAE7C,UAAM,YAAY,cAAc,WAAW;AAC3C,UAAMC,IAAG,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;AAC7C,UAAMA,IAAG,UAAUD,MAAK,KAAK,WAAW,cAAc,GAAG,MAAM;AAE/D,QAAI,CAAC,SAAS;AACZ,aAAO,aAAa;AAAA,QAClB,SAAS;AAAA,QACT,SAAS;AAAA,QACT,MAAM,UAAU,cAAc,cAAc,MAAM,EAAE,GAAG,cAAc;AAAA,MACvE,CAAC;AAAA,IACH;AAGA,QAAI,WAAW,QAAQ,QAAQ;AAE7B,UAAI;AACF,cAAM,WAAW,MAAME,OAAM,MAAM,EAAE,SAAS;AAC9C,aAAK,QAAQ,IAAI;AAAA,UACf,GAAG,EAAE,GAAG,SAAS,SAAS,GAAG,GAAG,SAAS,UAAU,EAAE;AAAA,QACvD;AAAA,MACF,QAAQ;AACN,aAAK,QAAQ,IAAI,EAAE,GAAG,EAAE,GAAG,GAAG,GAAG,EAAE,EAAE;AAAA,MACvC;AAAA,IACF,OAAO;AAEL,WAAK,QAAQ,IAAI,CAAC;AAAA,IACpB;AAEA,UAAM,SAAS,IAAI;AAEnB,WAAO,aAAa;AAAA,MAClB,SAAS;AAAA,MACT;AAAA,MACA,SAAS;AAAA,IACX,CAAC;AAAA,EACH,SAAS,OAAO;AACd,YAAQ,MAAM,qBAAqB,KAAK;AACxC,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,WAAO,aAAa,EAAE,OAAO,0BAA0B,OAAO,GAAG,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACrF;AACF;AAEA,eAAsB,aAAa,SAAkB;AACnD,MAAI;AACF,UAAM,EAAE,MAAM,IAAI,MAAM,QAAQ,KAAK;AAErC,QAAI,CAAC,SAAS,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,GAAG;AACzD,aAAO,aAAa,EAAE,OAAO,oBAAoB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACrE;AAEA,UAAM,OAAO,MAAM,SAAS;AAC5B,UAAM,UAAoB,CAAC;AAC3B,UAAM,SAAmB,CAAC;AAC1B,UAAM,gBAAgB,oBAAI,IAAY;AAEtC,eAAW,YAAY,OAAO;AAC5B,UAAI;AACF,YAAI,CAAC,SAAS,WAAW,SAAS,GAAG;AACnC,iBAAO,KAAK,iBAAiB,QAAQ,EAAE;AACvC;AAAA,QACF;AAEA,cAAM,eAAe,iBAAiB,QAAQ;AAC9C,cAAM,WAAW,MAAM,SAAS,QAAQ,aAAa,EAAE;AAGvD,sBAAc,IAAIF,MAAK,QAAQ,YAAY,CAAC;AAG5C,cAAM,QAAQ,KAAK,QAAQ;AAC3B,cAAM,kBAAkB,OAAO,MAAM;AAGrC,YAAI;AACF,gBAAM,QAAQ,MAAMC,IAAG,KAAK,YAAY;AAExC,cAAI,MAAM,YAAY,GAAG;AACvB,kBAAMA,IAAG,GAAG,cAAc,EAAE,WAAW,KAAK,CAAC;AAG7C,kBAAM,SAAS,WAAW;AAC1B,uBAAW,OAAO,OAAO,KAAK,IAAI,GAAG;AACnC,kBAAI,IAAI,WAAW,MAAM,KAAK,QAAQ,UAAU;AAC9C,sBAAM,WAAW,KAAK,GAAG;AAEzB,oBAAI,YAAY,SAAS,MAAM,QAAW;AACxC,6BAAW,aAAa,qBAAqB,GAAG,GAAG;AACjD,0BAAM,oBAAoB,cAAc,SAAS;AACjD,wBAAI;AAAE,4BAAMA,IAAG,OAAO,iBAAiB;AAAA,oBAAE,QAAQ;AAAA,oBAAe;AAAA,kBAClE;AAAA,gBACF;AACA,uBAAO,KAAK,GAAG;AAAA,cACjB;AAAA,YACF;AAAA,UACF,OAAO;AACL,kBAAMA,IAAG,OAAO,YAAY;AAE5B,kBAAM,mBAAmB,SAAS,WAAW,gBAAgB;AAE7D,gBAAI,CAAC,oBAAoB,OAAO;AAE9B,kBAAI,CAAC,iBAAiB;AACpB,2BAAW,aAAa,qBAAqB,QAAQ,GAAG;AACtD,wBAAM,oBAAoB,cAAc,SAAS;AACjD,sBAAI;AAAE,0BAAMA,IAAG,OAAO,iBAAiB;AAAA,kBAAE,QAAQ;AAAA,kBAAe;AAAA,gBAClE;AAAA,cACF;AACA,qBAAO,KAAK,QAAQ;AAAA,YACtB;AAAA,UACF;AAAA,QACF,QAAQ;AAEN,cAAI,OAAO;AAET,mBAAO,KAAK,QAAQ;AAAA,UACtB,OAAO;AAEL,kBAAM,SAAS,WAAW;AAC1B,gBAAI,WAAW;AACf,uBAAW,OAAO,OAAO,KAAK,IAAI,GAAG;AACnC,kBAAI,IAAI,WAAW,MAAM,GAAG;AAC1B,uBAAO,KAAK,GAAG;AACf,2BAAW;AAAA,cACb;AAAA,YACF;AACA,gBAAI,CAAC,UAAU;AACb,qBAAO,KAAK,cAAc,QAAQ,EAAE;AACpC;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAEA,gBAAQ,KAAK,QAAQ;AAAA,MACvB,SAAS,OAAO;AACd,gBAAQ,MAAM,oBAAoB,QAAQ,KAAK,KAAK;AACpD,eAAO,KAAK,QAAQ;AAAA,MACtB;AAAA,IACF;AAEA,UAAM,SAAS,IAAI;AAGnB,eAAW,UAAU,eAAe;AAClC,YAAM,mBAAmB,MAAM;AAAA,IACjC;AAEA,WAAO,aAAa;AAAA,MAClB,SAAS;AAAA,MACT;AAAA,MACA,QAAQ,OAAO,SAAS,IAAI,SAAS;AAAA,IACvC,CAAC;AAAA,EACH,SAAS,OAAO;AACd,YAAQ,MAAM,qBAAqB,KAAK;AACxC,WAAO,aAAa,EAAE,OAAO,yBAAyB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC1E;AACF;AAEA,eAAsB,mBAAmB,SAAkB;AACzD,MAAI;AACF,UAAM,EAAE,YAAY,KAAK,IAAI,MAAM,QAAQ,KAAK;AAEhD,QAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;AACrC,aAAO,aAAa,EAAE,OAAO,0BAA0B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC3E;AAEA,UAAM,gBAAgB,KAAK,QAAQ,iBAAiB,EAAE,EAAE,KAAK;AAC7D,QAAI,CAAC,eAAe;AAClB,aAAO,aAAa,EAAE,OAAO,sBAAsB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACvE;AAEA,UAAM,YAAY,cAAc,UAAU,QAAQ,SAAS,EAAE;AAC7D,UAAM,aAAa,iBAAiB,UAAU,aAAa;AAE3D,QAAI,CAAC,WAAW,WAAW,cAAc,CAAC,GAAG;AAC3C,aAAO,aAAa,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAChE;AAEA,QAAI;AACF,YAAMA,IAAG,OAAO,UAAU;AAC1B,aAAO,aAAa,EAAE,OAAO,yCAAyC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC1F,QAAQ;AAAA,IAER;AAEA,UAAMA,IAAG,MAAM,YAAY,EAAE,WAAW,KAAK,CAAC;AAE9C,WAAO,aAAa,EAAE,SAAS,MAAM,MAAMD,MAAK,KAAK,UAAU,aAAa,EAAE,CAAC;AAAA,EACjF,SAAS,OAAO;AACd,YAAQ,MAAM,4BAA4B,KAAK;AAC/C,WAAO,aAAa,EAAE,OAAO,0BAA0B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC3E;AACF;AAEA,eAAsB,aAAa,SAAkB;AACnD,MAAI;AACF,UAAM,EAAE,SAAS,QAAQ,IAAI,MAAM,QAAQ,KAAK;AAEhD,QAAI,CAAC,WAAW,CAAC,SAAS;AACxB,aAAO,aAAa,EAAE,OAAO,iCAAiC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAClF;AAEA,UAAM,gBAAgB,QAAQ,QAAQ,iBAAiB,EAAE,EAAE,KAAK;AAChE,QAAI,CAAC,eAAe;AAClB,aAAO,aAAa,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAChE;AAEA,UAAM,WAAW,QAAQ,QAAQ,SAAS,EAAE;AAC5C,UAAM,kBAAkB,iBAAiB,QAAQ;AACjD,UAAM,YAAYA,MAAK,QAAQ,eAAe;AAC9C,UAAM,kBAAkBA,MAAK,KAAK,WAAW,aAAa;AAE1D,QAAI,CAAC,gBAAgB,WAAW,cAAc,CAAC,GAAG;AAChD,aAAO,aAAa,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAChE;AAEA,QAAI;AACF,YAAMC,IAAG,OAAO,eAAe;AAAA,IACjC,QAAQ;AACN,aAAO,aAAa,EAAE,OAAO,2BAA2B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC5E;AAEA,QAAI;AACF,YAAMA,IAAG,OAAO,eAAe;AAC/B,aAAO,aAAa,EAAE,OAAO,wCAAwC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACzF,QAAQ;AAAA,IAER;AAEA,UAAM,QAAQ,MAAMA,IAAG,KAAK,eAAe;AAC3C,UAAM,SAAS,MAAM,OAAO;AAC5B,UAAM,UAAU,UAAU,YAAYD,MAAK,SAAS,OAAO,CAAC;AAE5D,UAAMC,IAAG,OAAO,iBAAiB,eAAe;AAEhD,QAAI,SAAS;AACX,YAAM,OAAO,MAAM,SAAS;AAC5B,YAAM,kBAAkB,SAAS,QAAQ,aAAa,EAAE;AACxD,YAAM,kBAAkBD,MAAK,KAAKA,MAAK,QAAQ,eAAe,GAAG,aAAa;AAC9E,YAAM,SAAS,MAAM;AACrB,YAAM,SAAS,MAAM;AAErB,UAAI,KAAK,MAAM,GAAG;AAChB,cAAM,QAAQ,KAAK,MAAM;AAEzB,cAAM,gBAAgB,qBAAqB,MAAM;AACjD,cAAM,gBAAgB,qBAAqB,MAAM;AAEjD,iBAAS,IAAI,GAAG,IAAI,cAAc,QAAQ,KAAK;AAC7C,gBAAM,eAAe,cAAc,cAAc,CAAC,CAAC;AACnD,gBAAM,eAAe,cAAc,cAAc,CAAC,CAAC;AAEnD,gBAAMC,IAAG,MAAMD,MAAK,QAAQ,YAAY,GAAG,EAAE,WAAW,KAAK,CAAC;AAE9D,cAAI;AACF,kBAAMC,IAAG,OAAO,cAAc,YAAY;AAAA,UAC5C,QAAQ;AAAA,UAER;AAAA,QACF;AAEA,eAAO,KAAK,MAAM;AAClB,aAAK,MAAM,IAAI;AAAA,MACjB;AAEA,YAAM,SAAS,IAAI;AAAA,IACrB;AAEA,UAAM,UAAUD,MAAK,KAAKA,MAAK,QAAQ,QAAQ,GAAG,aAAa;AAC/D,WAAO,aAAa,EAAE,SAAS,MAAM,QAAQ,CAAC;AAAA,EAChD,SAAS,OAAO;AACd,YAAQ,MAAM,qBAAqB,KAAK;AACxC,WAAO,aAAa,EAAE,OAAO,mBAAmB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACpE;AACF;AAEA,eAAsB,iBAAiB,SAAkB;AACvD,QAAM,UAAU,IAAI,YAAY;AAEhC,QAAM,SAAS,IAAI,eAAe;AAAA,IAChC,MAAM,MAAM,YAAY;AACtB,YAAM,YAAY,CAAC,SAAiB;AAClC,mBAAW,QAAQ,QAAQ,OAAO,SAAS,KAAK,UAAU,IAAI,CAAC;AAAA;AAAA,CAAM,CAAC;AAAA,MACxE;AAEA,UAAI;AACF,cAAM,EAAE,OAAO,YAAY,IAAI,MAAM,QAAQ,KAAK;AAElD,YAAI,CAAC,SAAS,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,GAAG;AACzD,oBAAU,EAAE,MAAM,SAAS,SAAS,qBAAqB,CAAC;AAC1D,qBAAW,MAAM;AACjB;AAAA,QACF;AAEA,YAAI,CAAC,eAAe,OAAO,gBAAgB,UAAU;AACnD,oBAAU,EAAE,MAAM,SAAS,SAAS,0BAA0B,CAAC;AAC/D,qBAAW,MAAM;AACjB;AAAA,QACF;AAEA,cAAM,kBAAkB,YAAY,QAAQ,SAAS,EAAE;AACvD,cAAM,sBAAsB,iBAAiB,eAAe;AAE5D,YAAI,CAAC,oBAAoB,WAAW,cAAc,CAAC,GAAG;AACpD,oBAAU,EAAE,MAAM,SAAS,SAAS,sBAAsB,CAAC;AAC3D,qBAAW,MAAM;AACjB;AAAA,QACF;AAGA,cAAMC,IAAG,MAAM,qBAAqB,EAAE,WAAW,KAAK,CAAC;AAEvD,cAAM,OAAO,MAAM,SAAS;AAC5B,cAAM,UAAU,WAAW,IAAI;AAC/B,cAAM,cAAc,QAAQ,IAAI,0BAA0B,QAAQ,OAAO,EAAE,KAAK;AAEhF,cAAM,QAAkB,CAAC;AACzB,cAAM,SAAmB,CAAC;AAC1B,cAAM,gBAAgB,oBAAI,IAAY;AACtC,cAAM,QAAQ,MAAM;AAEpB,kBAAU,EAAE,MAAM,SAAS,MAAM,CAAC;AAElC,iBAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,gBAAM,WAAW,MAAM,CAAC;AACxB,gBAAM,WAAW,SAAS,QAAQ,SAAS,EAAE;AAC7C,gBAAM,WAAWD,MAAK,SAAS,QAAQ;AACvC,gBAAM,kBAAkBA,MAAK,KAAK,qBAAqB,QAAQ;AAG/D,gBAAM,kBAAkB,SAAS,QAAQ,cAAc,EAAE;AACzD,gBAAM,oBAAoB,gBAAgB,QAAQ,cAAc,EAAE;AAClE,gBAAM,kBAAkB,oBAAoBA,MAAK,KAAK,mBAAmB,QAAQ,IAAI;AACrF,gBAAM,SAAS,MAAM;AACrB,gBAAM,SAAS,MAAM;AAErB,oBAAU;AAAA,YACR,MAAM;AAAA,YACN,SAAS,IAAI;AAAA,YACb;AAAA,YACA,SAAS,KAAK,OAAQ,IAAI,KAAK,QAAS,GAAG;AAAA,YAC3C,aAAa;AAAA,UACf,CAAC;AAGD,cAAI,KAAK,MAAM,GAAG;AAChB,mBAAO,KAAK,GAAG,QAAQ,gCAAgC;AACvD;AAAA,UACF;AAEA,gBAAM,QAAQ,KAAK,MAAM;AACzB,gBAAM,UAAU,YAAY,QAAQ;AAGpC,gBAAM,YAAY,OAAO,MAAM;AAC/B,gBAAM,aAAa,aAAa,MAAM,MAAM,SAAY,QAAQ,MAAM,CAAC,IAAI;AAC3E,gBAAM,WAAW,cAAc,CAAC,eAAe,eAAe;AAC9D,gBAAM,eAAe,aAAa,eAAe,eAAe;AAChE,gBAAM,yBAAyB,YAAY,KAAK;AAEhD,cAAI;AAEF,kBAAM,eAAeA,MAAK,QAAQ,iBAAiB,QAAQ,CAAC;AAC5D,0BAAc,IAAI,YAAY;AAE9B,gBAAI,YAAY,SAAS;AAEvB,oBAAM,YAAY,GAAG,UAAU,GAAG,MAAM;AACxC,oBAAM,SAAS,MAAM,sBAAsB,SAAS;AAEpD,oBAAMC,IAAG,MAAMD,MAAK,QAAQ,eAAe,GAAG,EAAE,WAAW,KAAK,CAAC;AACjE,oBAAMC,IAAG,UAAU,iBAAiB,MAAM;AAE1C,oBAAM,WAAsB;AAAA,gBAC1B,GAAG,OAAO;AAAA,gBACV,GAAG,OAAO;AAAA,cACZ;AACA,qBAAO,KAAK,MAAM;AAClB,mBAAK,MAAM,IAAI;AACf,oBAAM,KAAK,QAAQ;AAAA,YAErB,WAAW,gBAAgB,SAAS;AAElC,oBAAM,SAAS,MAAM,gBAAgB,MAAM;AAE3C,oBAAMA,IAAG,MAAMD,MAAK,QAAQ,eAAe,GAAG,EAAE,WAAW,KAAK,CAAC;AACjE,oBAAMC,IAAG,UAAU,iBAAiB,MAAM;AAE1C,kBAAI,WAAsB;AAAA,gBACxB,GAAG,OAAO;AAAA,gBACV,GAAG,OAAO;AAAA,cACZ;AAEA,kBAAI,wBAAwB;AAC1B,sBAAM,iBAAiB,MAAM,aAAa,QAAQ,MAAM;AACxD,2BAAW,EAAE,GAAG,UAAU,GAAG,eAAe;AAAA,cAC9C;AAEA,oBAAM,oBAAoB,MAAM;AAEhC,kBAAI,wBAAwB;AAC1B,sBAAM,YAAY,MAAM;AAAA,cAC1B;AAEA,oBAAM,cAAc,QAAQ,sBAAsB;AAElD,kBAAI;AAAE,sBAAMA,IAAG,OAAO,eAAe;AAAA,cAAE,QAAQ;AAAA,cAAe;AAC9D,kBAAI,wBAAwB;AAC1B,sBAAM,sBAAsB,MAAM;AAAA,cACpC;AAEA,uBAAS,IAAI,OAAO;AAEpB,qBAAO,KAAK,MAAM;AAClB,mBAAK,MAAM,IAAI;AACf,oBAAM,KAAK,QAAQ;AAAA,YAErB,OAAO;AAEL,oBAAM,eAAe,iBAAiB,QAAQ;AAE9C,kBAAI,oBAAoB,WAAW,eAAeD,MAAK,GAAG,GAAG;AAC3D,uBAAO,KAAK,eAAe,QAAQ,cAAc;AACjD;AAAA,cACF;AAEA,kBAAI;AACF,sBAAMC,IAAG,OAAO,YAAY;AAAA,cAC9B,QAAQ;AACN,uBAAO,KAAK,GAAG,QAAQ,YAAY;AACnC;AAAA,cACF;AAEA,kBAAI;AACF,sBAAMA,IAAG,OAAO,eAAe;AAC/B,uBAAO,KAAK,GAAG,QAAQ,gCAAgC;AACvD;AAAA,cACF,QAAQ;AAAA,cAER;AAEA,oBAAMA,IAAG,OAAO,cAAc,eAAe;AAE7C,oBAAM,QAAQ,MAAMA,IAAG,KAAK,eAAe;AAC3C,kBAAI,MAAM,OAAO,KAAK,WAAW,OAAO;AACtC,sBAAM,gBAAgB,qBAAqB,MAAM;AACjD,sBAAM,gBAAgB,qBAAqB,MAAM;AAEjD,yBAAS,IAAI,GAAG,IAAI,cAAc,QAAQ,KAAK;AAC7C,wBAAM,eAAe,cAAc,cAAc,CAAC,CAAC;AACnD,wBAAM,eAAe,cAAc,cAAc,CAAC,CAAC;AAEnD,sBAAI;AAEF,0BAAMA,IAAG,OAAO,YAAY;AAG5B,kCAAc,IAAID,MAAK,QAAQ,YAAY,CAAC;AAG5C,0BAAMC,IAAG,MAAMD,MAAK,QAAQ,YAAY,GAAG,EAAE,WAAW,KAAK,CAAC;AAC9D,0BAAMC,IAAG,OAAO,cAAc,YAAY;AAAA,kBAC5C,QAAQ;AAAA,kBAER;AAAA,gBACF;AAEA,uBAAO,KAAK,MAAM;AAClB,qBAAK,MAAM,IAAI;AAAA,cACjB,WAAW,MAAM,YAAY,GAAG;AAC9B,sBAAM,YAAY,SAAS;AAC3B,sBAAM,YAAY,SAAS;AAE3B,2BAAW,OAAO,OAAO,KAAK,IAAI,GAAG;AACnC,sBAAI,IAAI,WAAW,SAAS,GAAG;AAC7B,0BAAM,aAAa,YAAY,IAAI,MAAM,UAAU,MAAM;AACzD,yBAAK,UAAU,IAAI,KAAK,GAAG;AAC3B,2BAAO,KAAK,GAAG;AAAA,kBACjB;AAAA,gBACF;AAAA,cACF;AAEA,oBAAM,KAAK,QAAQ;AAAA,YACrB;AAAA,UACF,SAAS,KAAK;AACZ,oBAAQ,MAAM,kBAAkB,QAAQ,KAAK,GAAG;AAChD,mBAAO,KAAK,kBAAkB,QAAQ,EAAE;AAAA,UAC1C;AAAA,QACF;AAEA,cAAM,SAAS,IAAI;AAGnB,mBAAW,UAAU,eAAe;AAClC,gBAAM,mBAAmB,MAAM;AAAA,QACjC;AAEA,kBAAU;AAAA,UACR,MAAM;AAAA,UACN,OAAO,MAAM;AAAA,UACb,QAAQ,OAAO;AAAA,UACf,eAAe;AAAA,QACjB,CAAC;AAAA,MACH,SAAS,OAAO;AACd,gBAAQ,MAAM,mBAAmB,KAAK;AACtC,kBAAU,EAAE,MAAM,SAAS,SAAS,uBAAuB,CAAC;AAAA,MAC9D,UAAE;AACA,mBAAW,MAAM;AAAA,MACnB;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO,IAAI,SAAS,QAAQ;AAAA,IAC1B,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,MACjB,cAAc;AAAA,IAChB;AAAA,EACF,CAAC;AACH;;;AEtlBA,SAAS,YAAYE,WAAU;AAC/B,OAAOC,WAAU;AACjB,SAAS,YAAAC,WAAU,oBAAAC,mBAAkB,uBAAAC,4BAA2B;AAsBhE,eAAsB,WAAW,SAAkB;AACjD,QAAM,YAAY,QAAQ,IAAI;AAC9B,QAAM,cAAc,QAAQ,IAAI;AAChC,QAAM,kBAAkB,QAAQ,IAAI;AACpC,QAAM,aAAa,QAAQ,IAAI;AAC/B,QAAM,YAAY,QAAQ,IAAI,0BAA0B,QAAQ,UAAU,EAAE;AAE5E,MAAI,CAAC,aAAa,CAAC,eAAe,CAAC,mBAAmB,CAAC,cAAc,CAAC,WAAW;AAC/E,WAAO;AAAA,MACL,EAAE,OAAO,gEAAgE;AAAA,MACzE,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,MAAI;AACF,UAAM,EAAE,UAAU,IAAI,MAAM,QAAQ,KAAK;AAEzC,QAAI,CAAC,aAAa,CAAC,MAAM,QAAQ,SAAS,KAAK,UAAU,WAAW,GAAG;AACrE,aAAO,aAAa,EAAE,OAAO,yBAAyB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC1E;AAEA,UAAM,OAAO,MAAM,SAAS;AAC5B,UAAM,UAAU,WAAW,IAAI;AAG/B,UAAM,WAAW,iBAAiB,MAAM,SAAS;AAEjD,UAAM,KAAK,IAAIC,UAAS;AAAA,MACtB,QAAQ;AAAA,MACR,UAAU,WAAW,SAAS;AAAA,MAC9B,aAAa,EAAE,aAAa,gBAAgB;AAAA,IAC9C,CAAC;AAED,UAAM,SAAmB,CAAC;AAC1B,UAAM,SAAmB,CAAC;AAC1B,UAAM,gBAAgB,oBAAI,IAAY;AAEtC,aAAS,YAAY,WAAW;AAE9B,UAAI,CAAC,SAAS,WAAW,GAAG,GAAG;AAC7B,mBAAW,IAAI,QAAQ;AAAA,MACzB;AAEA,YAAM,QAAQ,aAAa,MAAM,QAAQ;AACzC,UAAI,CAAC,OAAO;AACV,eAAO,KAAK,4BAA4B,QAAQ,mBAAmB;AACnE;AAAA,MACF;AAGA,YAAM,iBAAiB,MAAM,MAAM,SAAY,QAAQ,MAAM,CAAC,IAAI;AAClE,YAAM,mBAAmB,mBAAmB;AAE5C,UAAI,kBAAkB;AACpB,eAAO,KAAK,QAAQ;AACpB;AAAA,MACF;AAGA,YAAM,WAAW,MAAM,MAAM,UAAa,mBAAmB;AAE7D,UAAI;AACF,YAAI;AAEJ,YAAI,UAAU;AAEZ,gBAAM,YAAY,GAAG,cAAc,GAAG,QAAQ;AAC9C,2BAAiB,MAAM,sBAAsB,SAAS;AAAA,QACxD,OAAO;AAEL,gBAAM,oBAAoB,cAAc,QAAQ;AAChD,cAAI;AACF,6BAAiB,MAAMC,IAAG,SAAS,iBAAiB;AAAA,UACtD,QAAQ;AACN,mBAAO,KAAK,4BAA4B,QAAQ,EAAE;AAClD;AAAA,UACF;AAAA,QACF;AAGA,cAAM,GAAG;AAAA,UACP,IAAIC,kBAAiB;AAAA,YACnB,QAAQ;AAAA,YACR,KAAK,SAAS,QAAQ,OAAO,EAAE;AAAA,YAC/B,MAAM;AAAA,YACN,aAAa,eAAe,QAAQ;AAAA,UACtC,CAAC;AAAA,QACH;AAGA,YAAI,CAAC,YAAY,YAAY,KAAK,GAAG;AACnC,qBAAW,aAAa,qBAAqB,QAAQ,GAAG;AACtD,kBAAM,YAAY,cAAc,SAAS;AACzC,gBAAI;AACF,oBAAM,aAAa,MAAMD,IAAG,SAAS,SAAS;AAC9C,oBAAM,GAAG;AAAA,gBACP,IAAIC,kBAAiB;AAAA,kBACnB,QAAQ;AAAA,kBACR,KAAK,UAAU,QAAQ,OAAO,EAAE;AAAA,kBAChC,MAAM;AAAA,kBACN,aAAa,eAAe,SAAS;AAAA,gBACvC,CAAC;AAAA,cACH;AAAA,YACF,QAAQ;AAAA,YAER;AAAA,UACF;AAAA,QACF;AAEA,cAAM,IAAI;AAGV,YAAI,CAAC,UAAU;AACb,gBAAM,oBAAoB,cAAc,QAAQ;AAGhD,wBAAc,IAAIC,MAAK,QAAQ,iBAAiB,CAAC;AAGjD,qBAAW,aAAa,qBAAqB,QAAQ,GAAG;AACtD,kBAAM,YAAY,cAAc,SAAS;AAEzC,0BAAc,IAAIA,MAAK,QAAQ,SAAS,CAAC;AACzC,gBAAI;AAAE,oBAAMF,IAAG,OAAO,SAAS;AAAA,YAAE,QAAQ;AAAA,YAAe;AAAA,UAC1D;AAGA,cAAI;AAAE,kBAAMA,IAAG,OAAO,iBAAiB;AAAA,UAAE,QAAQ;AAAA,UAAe;AAAA,QAClE;AAEA,eAAO,KAAK,QAAQ;AAAA,MACtB,SAAS,OAAO;AACd,gBAAQ,MAAM,kBAAkB,QAAQ,KAAK,KAAK;AAClD,eAAO,KAAK,mBAAmB,QAAQ,EAAE;AAAA,MAC3C;AAAA,IACF;AAEA,UAAM,SAAS,IAAI;AAGnB,eAAW,UAAU,eAAe;AAClC,YAAM,mBAAmB,MAAM;AAAA,IACjC;AAEA,WAAO,aAAa;AAAA,MAClB,SAAS;AAAA,MACT;AAAA,MACA,QAAQ,OAAO,SAAS,IAAI,SAAS;AAAA,IACvC,CAAC;AAAA,EACH,SAAS,OAAO;AACd,YAAQ,MAAM,mBAAmB,KAAK;AACtC,WAAO,aAAa,EAAE,OAAO,wBAAwB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACzE;AACF;AA+FA,eAAsB,sBAAsB,SAAkB;AAC5D,QAAM,YAAY,QAAQ,IAAI,0BAA0B,QAAQ,UAAU,EAAE;AAC5E,QAAM,UAAU,IAAI,YAAY;AAGhC,MAAI;AACJ,MAAI;AACF,UAAM,OAAO,MAAM,QAAQ,KAAK;AAChC,gBAAY,KAAK;AAEjB,QAAI,CAAC,aAAa,CAAC,MAAM,QAAQ,SAAS,KAAK,UAAU,WAAW,GAAG;AACrE,aAAO,aAAa,EAAE,OAAO,yBAAyB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC1E;AAAA,EACF,QAAQ;AACN,WAAO,aAAa,EAAE,OAAO,uBAAuB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACxE;AAEA,QAAM,SAAS,IAAI,eAAe;AAAA,IAChC,MAAM,MAAM,YAAY;AACtB,YAAM,YAAY,CAAC,SAAiB;AAClC,mBAAW,QAAQ,QAAQ,OAAO,SAAS,KAAK,UAAU,IAAI,CAAC;AAAA;AAAA,CAAM,CAAC;AAAA,MACxE;AAEA,UAAI;AACF,cAAM,OAAO,MAAM,SAAS;AAC5B,cAAM,UAAU,WAAW,IAAI;AAC/B,cAAM,UAAoB,CAAC;AAC3B,cAAM,UAAoB,CAAC;AAC3B,cAAM,SAAmB,CAAC;AAE1B,cAAM,QAAQ,UAAU;AACxB,kBAAU,EAAE,MAAM,SAAS,MAAM,CAAC;AAElC,iBAAS,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;AACzC,cAAI,WAAW,UAAU,CAAC;AAG1B,cAAI,CAAC,SAAS,WAAW,GAAG,GAAG;AAC7B,uBAAW,IAAI,QAAQ;AAAA,UACzB;AAEA,oBAAU;AAAA,YACR,MAAM;AAAA,YACN,SAAS,IAAI;AAAA,YACb;AAAA,YACA,SAAS,KAAK,OAAQ,IAAI,KAAK,QAAS,GAAG;AAAA,YAC3C,SAAS,2BAA2B,SAAS,MAAM,CAAC,CAAC;AAAA,UACvD,CAAC;AAED,cAAI;AACF,kBAAM,QAAQ,aAAa,MAAM,QAAQ;AACzC,gBAAI,CAAC,OAAO;AACV,qBAAO,KAAK,QAAQ;AACpB;AAAA,YACF;AAGA,kBAAM,gBAAgB,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM;AAChE,gBAAI,CAAC,eAAe;AAClB,sBAAQ,KAAK,QAAQ;AACrB;AAAA,YACF;AAEA,kBAAM,mBAAmB,MAAM;AAC/B,kBAAM,iBAAiB,qBAAqB,SAAY,QAAQ,gBAAgB,IAAI;AACpF,kBAAM,YAAY,mBAAmB;AAGrC,kBAAM,sBAAsB,QAAQ;AAGpC,gBAAI,WAAW;AACb,oBAAM,wBAAwB,QAAQ;AAAA,YACxC;AAGA,iBAAK,QAAQ,IAAI;AAAA,cACf,GAAG,MAAM;AAAA,cACT,GAAG,MAAM;AAAA,cACT,GAAI,MAAM,MAAM,SAAY,EAAE,GAAG,MAAM,EAAE,IAAI,CAAC;AAAA,YAChD;AAEA,oBAAQ,KAAK,QAAQ;AAAA,UACvB,SAAS,OAAO;AACd,oBAAQ,MAAM,uBAAuB,QAAQ,KAAK,KAAK;AACvD,mBAAO,KAAK,QAAQ;AAAA,UACtB;AAAA,QACF;AAEA,kBAAU,EAAE,MAAM,WAAW,SAAS,qBAAqB,CAAC;AAC5D,cAAM,SAAS,IAAI;AAGnB,kBAAU,EAAE,MAAM,WAAW,SAAS,+BAA+B,CAAC;AAEtE,cAAM,YAAY,cAAc,QAAQ;AACxC,YAAI;AACF,gBAAM,6BAA6B,SAAS;AAAA,QAC9C,QAAQ;AAAA,QAER;AAGA,YAAI,UAAU,2BAA2B,QAAQ,MAAM,SAAS,QAAQ,WAAW,IAAI,MAAM,EAAE;AAC/F,YAAI,QAAQ,SAAS,GAAG;AACtB,qBAAW,IAAI,QAAQ,MAAM,SAAS,QAAQ,WAAW,IAAI,MAAM,EAAE;AAAA,QACvE;AACA,YAAI,OAAO,SAAS,GAAG;AACrB,qBAAW,IAAI,OAAO,MAAM,SAAS,OAAO,WAAW,IAAI,MAAM,EAAE;AAAA,QACrE;AAEA,kBAAU;AAAA,UACR,MAAM;AAAA,UACN,WAAW,QAAQ;AAAA,UACnB,SAAS,QAAQ;AAAA,UACjB,QAAQ,OAAO;AAAA,UACf;AAAA,QACF,CAAC;AAED,mBAAW,MAAM;AAAA,MACnB,SAAS,OAAO;AACd,gBAAQ,MAAM,2BAA2B,KAAK;AAC9C,kBAAU,EAAE,MAAM,SAAS,SAAS,8BAA8B,CAAC;AACnE,mBAAW,MAAM;AAAA,MACnB;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO,IAAI,SAAS,QAAQ;AAAA,IAC1B,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,MACjB,YAAY;AAAA,IACd;AAAA,EACF,CAAC;AACH;AAEA,eAAsB,sBAAsB,SAAkB;AAC5D,QAAM,YAAY,QAAQ,IAAI,0BAA0B,QAAQ,UAAU,EAAE;AAC5E,QAAM,UAAU,IAAI,YAAY;AAGhC,MAAI;AACJ,MAAI;AACF,UAAM,OAAO,MAAM,QAAQ,KAAK;AAChC,gBAAY,KAAK;AAEjB,QAAI,CAAC,aAAa,CAAC,MAAM,QAAQ,SAAS,KAAK,UAAU,WAAW,GAAG;AACrE,aAAO,aAAa,EAAE,OAAO,yBAAyB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC1E;AAAA,EACF,QAAQ;AACN,WAAO,aAAa,EAAE,OAAO,uBAAuB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACxE;AAEA,QAAM,SAAS,IAAI,eAAe;AAAA,IAChC,MAAM,MAAM,YAAY;AACtB,YAAM,YAAY,CAAC,SAAiB;AAClC,mBAAW,QAAQ,QAAQ,OAAO,SAAS,KAAK,UAAU,IAAI,CAAC;AAAA;AAAA,CAAM,CAAC;AAAA,MACxE;AAEA,UAAI;AACF,cAAM,OAAO,MAAM,SAAS;AAC5B,cAAM,UAAU,WAAW,IAAI;AAC/B,cAAM,YAAsB,CAAC;AAC7B,cAAM,SAAmB,CAAC;AAE1B,cAAM,QAAQ,UAAU;AACxB,kBAAU,EAAE,MAAM,SAAS,MAAM,CAAC;AAElC,iBAAS,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;AACzC,cAAI,WAAW,UAAU,CAAC;AAG1B,cAAI,CAAC,SAAS,WAAW,GAAG,GAAG;AAC7B,uBAAW,IAAI,QAAQ;AAAA,UACzB;AAEA,oBAAU;AAAA,YACR,MAAM;AAAA,YACN,SAAS,IAAI;AAAA,YACb;AAAA,YACA,SAAS,KAAK,OAAQ,IAAI,KAAK,QAAS,GAAG;AAAA,YAC3C,SAAS,cAAc,SAAS,MAAM,CAAC,CAAC;AAAA,UAC1C,CAAC;AAED,cAAI;AACF,gBAAI;AACJ,kBAAM,QAAQ,aAAa,MAAM,QAAQ;AACzC,kBAAM,mBAAmB,OAAO;AAChC,kBAAM,iBAAiB,qBAAqB,SAAY,QAAQ,gBAAgB,IAAI;AAGpF,kBAAM,YAAY,mBAAmB;AACrC,kBAAM,WAAW,qBAAqB,UAAa,CAAC;AAEpD,kBAAM,eAAe,cAAc,QAAQ;AAE3C,gBAAI;AACF,uBAAS,MAAMG,IAAG,SAAS,YAAY;AAAA,YACzC,QAAQ;AACN,kBAAI,WAAW;AACb,yBAAS,MAAM,gBAAgB,QAAQ;AACvC,sBAAM,MAAMC,MAAK,QAAQ,YAAY;AACrC,sBAAMD,IAAG,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AACvC,sBAAMA,IAAG,UAAU,cAAc,MAAM;AAAA,cACzC,WAAW,YAAY,gBAAgB;AACrC,sBAAM,YAAY,GAAG,cAAc,GAAG,QAAQ;AAC9C,yBAAS,MAAM,sBAAsB,SAAS;AAC9C,sBAAM,MAAMC,MAAK,QAAQ,YAAY;AACrC,sBAAMD,IAAG,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AACvC,sBAAMA,IAAG,UAAU,cAAc,MAAM;AAAA,cACzC,OAAO;AACL,sBAAM,IAAI,MAAM,mBAAmB,QAAQ,EAAE;AAAA,cAC/C;AAAA,YACF;AAEA,kBAAM,MAAMC,MAAK,QAAQ,QAAQ,EAAE,YAAY;AAC/C,kBAAM,QAAQ,QAAQ;AAEtB,gBAAI,OAAO;AACT,oBAAM,WAAWA,MAAK,QAAQ,SAAS,MAAM,CAAC,CAAC;AAC/C,oBAAM,aAAa,cAAc,UAAU,aAAa,MAAM,KAAK,QAAQ;AAC3E,oBAAMD,IAAG,MAAM,YAAY,EAAE,WAAW,KAAK,CAAC;AAE9C,oBAAM,WAAWC,MAAK,SAAS,QAAQ;AACvC,oBAAM,WAAWA,MAAK,KAAK,YAAY,QAAQ;AAC/C,oBAAMD,IAAG,UAAU,UAAU,MAAM;AAEnC,mBAAK,QAAQ,IAAI;AAAA,gBACf,GAAG;AAAA,gBACH,GAAG,EAAE,GAAG,GAAG,GAAG,EAAE;AAAA,gBAChB,GAAG;AAAA,gBACH,GAAG,EAAE,GAAG,GAAG,GAAG,EAAE;AAAA,cAClB;AAEA,kBAAI,UAAU;AACZ,uBAAQ,KAAK,QAAQ,EAAmC;AAAA,cAC1D;AAAA,YACF,OAAO;AACL,oBAAM,eAAe,MAAM,aAAa,QAAQ,QAAQ;AAExD,kBAAI,WAAW;AACb,6BAAa,IAAI;AAEjB,sBAAM,wBAAwB,QAAQ;AACtC,sBAAM,YAAY,QAAQ;AAE1B,sBAAM,sBAAsB,QAAQ;AACpC,oBAAI;AAAE,wBAAMA,IAAG,OAAO,YAAY;AAAA,gBAAE,QAAQ;AAAA,gBAAe;AAAA,cAC7D;AAEA,mBAAK,QAAQ,IAAI;AAAA,YACnB;AAEA,sBAAU,KAAK,QAAQ;AAAA,UACzB,SAAS,OAAO;AACd,oBAAQ,MAAM,uBAAuB,QAAQ,KAAK,KAAK;AACvD,mBAAO,KAAK,QAAQ;AAAA,UACtB;AAAA,QACF;AAEA,kBAAU,EAAE,MAAM,WAAW,SAAS,qBAAqB,CAAC;AAC5D,cAAM,SAAS,IAAI;AAGnB,YAAI,UAAU,4BAA4B,UAAU,MAAM,SAAS,UAAU,WAAW,IAAI,MAAM,EAAE;AACpG,YAAI,OAAO,SAAS,GAAG;AACrB,qBAAW,IAAI,OAAO,MAAM,SAAS,OAAO,WAAW,IAAI,MAAM,EAAE;AAAA,QACrE;AAEA,kBAAU;AAAA,UACR,MAAM;AAAA,UACN,WAAW,UAAU;AAAA,UACrB,QAAQ,OAAO;AAAA,UACf;AAAA,QACF,CAAC;AAED,mBAAW,MAAM;AAAA,MACnB,SAAS,OAAO;AACd,gBAAQ,MAAM,2BAA2B,KAAK;AAC9C,kBAAU,EAAE,MAAM,SAAS,SAAS,gCAAgC,CAAC;AACrE,mBAAW,MAAM;AAAA,MACnB;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO,IAAI,SAAS,QAAQ;AAAA,IAC1B,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,MACjB,YAAY;AAAA,IACd;AAAA,EACF,CAAC;AACH;AA8OA,eAAsB,qBAAqB,SAAkB;AAC3D,QAAM,EAAE,UAAU,IAAI,MAAM,QAAQ,KAAK;AAEzC,MAAI,CAAC,aAAa,CAAC,MAAM,QAAQ,SAAS,KAAK,UAAU,WAAW,GAAG;AACrE,WAAO,aAAa,EAAE,OAAO,yBAAyB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC1E;AAEA,QAAM,SAAS,IAAI,eAAe;AAAA,IAChC,MAAM,MAAM,YAAY;AACtB,YAAM,UAAU,IAAI,YAAY;AAChC,YAAM,YAAY,CAAC,SAAkC;AACnD,mBAAW,QAAQ,QAAQ,OAAO,SAAS,KAAK,UAAU,IAAI,CAAC;AAAA;AAAA,CAAM,CAAC;AAAA,MACxE;AAEA,gBAAU,EAAE,MAAM,SAAS,OAAO,UAAU,OAAO,CAAC;AAEpD,YAAM,aAAuB,CAAC;AAC9B,YAAM,UAAoB,CAAC;AAC3B,YAAM,SAAmB,CAAC;AAE1B,UAAI;AACF,cAAM,OAAO,MAAM,SAAS;AAE5B,iBAAS,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;AACzC,gBAAM,WAAW,UAAU,CAAC;AAC5B,gBAAM,QAAQ,aAAa,MAAM,QAAQ;AAEzC,cAAI,CAAC,SAAS,MAAM,MAAM,QAAW;AACnC,oBAAQ,KAAK,QAAQ;AACrB,sBAAU;AAAA,cACR,MAAM;AAAA,cACN,SAAS,IAAI;AAAA,cACb,OAAO,UAAU;AAAA,cACjB,SAAS,WAAW,QAAQ;AAAA,YAC9B,CAAC;AACD;AAAA,UACF;AAEA,cAAI;AAEF,kBAAM,cAAc,MAAM,gBAAgB,QAAQ;AAGlD,kBAAM,YAAY,cAAc,SAAS,QAAQ,OAAO,EAAE,CAAC;AAC3D,kBAAME,IAAG,MAAMC,MAAK,QAAQ,SAAS,GAAG,EAAE,WAAW,KAAK,CAAC;AAG3D,kBAAMD,IAAG,UAAU,WAAW,WAAW;AAGzC,kBAAM,wBAAwB,QAAQ;AAGtC,kBAAM,eAAe,YAAY,KAAK;AAGtC,mBAAO,MAAM;AAGb,gBAAI,cAAc;AAChB,oBAAM,iBAAiB,MAAM,aAAa,aAAa,QAAQ;AAE/D,oBAAM,KAAK,eAAe;AAC1B,oBAAM,KAAK,eAAe;AAC1B,oBAAM,KAAK,eAAe;AAC1B,oBAAM,IAAI,eAAe;AAAA,YAC3B;AAEA,uBAAW,KAAK,QAAQ;AACxB,sBAAU;AAAA,cACR,MAAM;AAAA,cACN,SAAS,IAAI;AAAA,cACb,OAAO,UAAU;AAAA,cACjB,SAAS,cAAc,QAAQ;AAAA,YACjC,CAAC;AAAA,UACH,SAAS,OAAO;AACd,oBAAQ,MAAM,sBAAsB,QAAQ,KAAK,KAAK;AACtD,mBAAO,KAAK,QAAQ;AACpB,sBAAU;AAAA,cACR,MAAM;AAAA,cACN,SAAS,IAAI;AAAA,cACb,OAAO,UAAU;AAAA,cACjB,SAAS,sBAAsB,QAAQ;AAAA,YACzC,CAAC;AAAA,UACH;AAAA,QACF;AAEA,cAAM,SAAS,IAAI;AAGnB,YAAI,UAAU,cAAc,WAAW,MAAM,SAAS,WAAW,WAAW,IAAI,MAAM,EAAE;AACxF,YAAI,QAAQ,SAAS,GAAG;AACtB,qBAAW,IAAI,QAAQ,MAAM,SAAS,QAAQ,WAAW,IAAI,WAAW,MAAM;AAAA,QAChF;AACA,YAAI,OAAO,SAAS,GAAG;AACrB,qBAAW,IAAI,OAAO,MAAM,SAAS,OAAO,WAAW,IAAI,MAAM,EAAE;AAAA,QACrE;AAEA,kBAAU;AAAA,UACR,MAAM;AAAA,UACN,YAAY,WAAW;AAAA,UACvB,SAAS,QAAQ;AAAA,UACjB,QAAQ,OAAO;AAAA,UACf;AAAA,QACF,CAAC;AAAA,MACH,SAAS,OAAO;AACd,gBAAQ,MAAM,0BAA0B,KAAK;AAC7C,kBAAU,EAAE,MAAM,SAAS,SAAS,4BAA4B,CAAC;AAAA,MACnE,UAAE;AACA,mBAAW,MAAM;AAAA,MACnB;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO,IAAI,SAAS,QAAQ;AAAA,IAC1B,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,MACjB,cAAc;AAAA,IAChB;AAAA,EACF,CAAC;AACH;AAMA,eAAsB,wBAAwB,SAAkB;AAC9D,QAAM,YAAY,QAAQ,IAAI;AAC9B,QAAM,cAAc,QAAQ,IAAI;AAChC,QAAM,kBAAkB,QAAQ,IAAI;AACpC,QAAM,aAAa,QAAQ,IAAI;AAC/B,QAAM,YAAY,QAAQ,IAAI,0BAA0B,QAAQ,OAAO,EAAE;AAEzE,QAAM,UAAU,IAAI,YAAY;AAEhC,QAAM,SAAS,IAAI,eAAe;AAAA,IAChC,MAAM,MAAM,YAAY;AACtB,YAAM,YAAY,CAAC,SAAiB;AAClC,mBAAW,QAAQ,QAAQ,OAAO,SAAS,KAAK,UAAU,IAAI,CAAC;AAAA;AAAA,CAAM,CAAC;AAAA,MACxE;AAEA,UAAI;AACF,YAAI,CAAC,aAAa,CAAC,eAAe,CAAC,mBAAmB,CAAC,cAAc,CAAC,WAAW;AAC/E,oBAAU,EAAE,MAAM,SAAS,SAAS,oBAAoB,CAAC;AACzD,qBAAW,MAAM;AACjB;AAAA,QACF;AAEA,cAAM,EAAE,MAAM,IAAI,MAAM,QAAQ,KAAK;AAErC,YAAI,CAAC,SAAS,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,GAAG;AACzD,oBAAU,EAAE,MAAM,SAAS,SAAS,oBAAoB,CAAC;AACzD,qBAAW,MAAM;AACjB;AAAA,QACF;AAEA,cAAM,KAAK,IAAIE,UAAS;AAAA,UACtB,QAAQ;AAAA,UACR,UAAU,WAAW,SAAS;AAAA,UAC9B,aAAa,EAAE,aAAa,gBAAgB;AAAA,QAC9C,CAAC;AAED,cAAM,OAAO,MAAM,SAAS;AAC5B,cAAM,UAAU,WAAW,IAAI;AAC/B,cAAM,cAAc,UAAU,QAAQ,OAAO,EAAE;AAE/C,cAAM,SAAmB,CAAC;AAC1B,cAAM,UAAoB,CAAC;AAC3B,cAAM,SAAmB,CAAC;AAC1B,cAAM,QAAQ,MAAM;AAEpB,kBAAU,EAAE,MAAM,SAAS,MAAM,CAAC;AAElC,iBAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,gBAAM,WAAW,MAAM,CAAC;AACxB,gBAAM,MAAM,SAAS,WAAW,SAAS,IAAI,MAAM,SAAS,MAAM,CAAC,IAAI;AACvE,gBAAM,QAAQ,KAAK,GAAG;AAEtB,oBAAU;AAAA,YACR,MAAM;AAAA,YACN,SAAS,IAAI;AAAA,YACb;AAAA,YACA,SAAS,KAAK,OAAQ,IAAI,KAAK,QAAS,GAAG;AAAA,YAC3C,aAAaD,MAAK,SAAS,GAAG;AAAA,UAChC,CAAC;AAED,cAAI,CAAC,SAAS,MAAM,MAAM,GAAG;AAC3B,oBAAQ,KAAK,GAAG;AAChB;AAAA,UACF;AAGA,gBAAM,aAAa,MAAM,MAAM,SAAY,QAAQ,MAAM,CAAC,GAAG,QAAQ,OAAO,EAAE,IAAI;AAClF,cAAI,CAAC,cAAc,eAAe,aAAa;AAC7C,oBAAQ,KAAK,GAAG;AAChB;AAAA,UACF;AAEA,cAAI;AAEF,kBAAM,YAAY,cAAc,GAAG;AACnC,kBAAM,SAAS,MAAMD,IAAG,SAAS,SAAS;AAC1C,kBAAM,cAAc,eAAeC,MAAK,SAAS,GAAG,CAAC;AAGrD,kBAAM,YAAY,IAAI,WAAW,GAAG,IAAI,IAAI,MAAM,CAAC,IAAI;AACvD,gBAAI;AACF,oBAAM,GAAG,KAAK,IAAIE,qBAAoB;AAAA,gBACpC,QAAQ;AAAA,gBACR,KAAK;AAAA,cACP,CAAC,CAAC;AAAA,YACJ,QAAQ;AAAA,YAER;AAGA,kBAAM,GAAG,KAAK,IAAIC,kBAAiB;AAAA,cACjC,QAAQ;AAAA,cACR,KAAK;AAAA,cACL,MAAM;AAAA,cACN,aAAa;AAAA,YACf,CAAC,CAAC;AAGF,gBAAI,YAAY,KAAK,GAAG;AAEtB,oBAAM,wBAAwB,GAAG;AAGjC,oBAAM,iBAAiB,MAAM,aAAa,QAAQ,GAAG;AACrD,qBAAO,OAAO,OAAO,cAAc;AAGnC,oBAAM,YAAY,GAAG;AAGrB,oBAAM,sBAAsB,GAAG;AAAA,YACjC;AAGA,kBAAMJ,IAAG,OAAO,SAAS;AAGzB,mBAAO,MAAM;AAEb,mBAAO,KAAK,GAAG;AAAA,UACjB,SAAS,OAAO;AACd,oBAAQ,MAAM,6BAA6B,GAAG,KAAK,KAAK;AACxD,mBAAO,KAAK,GAAG;AAAA,UACjB;AAAA,QACF;AAGA,kBAAU,EAAE,MAAM,WAAW,SAAS,iBAAiB,CAAC;AACxD,mBAAW,YAAY,QAAQ;AAC7B,gBAAM,YAAY,cAAc,QAAQ;AACxC,gBAAM,mBAAmBC,MAAK,QAAQ,SAAS,CAAC;AAAA,QAClD;AAEA,cAAM,SAAS,IAAI;AAEnB,YAAI,UAAU,UAAU,OAAO,MAAM,UAAU,OAAO,WAAW,IAAI,MAAM,EAAE;AAC7E,YAAI,QAAQ,SAAS,GAAG;AACtB,qBAAW,IAAI,QAAQ,MAAM,QAAQ,QAAQ,WAAW,IAAI,MAAM,EAAE;AAAA,QACtE;AACA,YAAI,OAAO,SAAS,GAAG;AACrB,qBAAW,IAAI,OAAO,MAAM,QAAQ,OAAO,WAAW,IAAI,MAAM,EAAE;AAAA,QACpE;AAEA,kBAAU;AAAA,UACR,MAAM;AAAA,UACN,QAAQ,OAAO;AAAA,UACf,SAAS,QAAQ;AAAA,UACjB,QAAQ,OAAO;AAAA,UACf;AAAA,QACF,CAAC;AAAA,MACH,SAAS,OAAO;AACd,gBAAQ,MAAM,uBAAuB,KAAK;AAC1C,kBAAU,EAAE,MAAM,SAAS,SAAS,yBAAyB,CAAC;AAAA,MAChE,UAAE;AACA,mBAAW,MAAM;AAAA,MACnB;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO,IAAI,SAAS,QAAQ;AAAA,IAC1B,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,MACjB,cAAc;AAAA,IAChB;AAAA,EACF,CAAC;AACH;AAKA,eAAsB,oBAAoB,SAAkB;AAC1D,MAAI;AACF,UAAM,EAAE,MAAM,IAAI,MAAM,QAAQ,KAAK;AAErC,QAAI,CAAC,SAAS,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,GAAG;AACzD,aAAO,aAAa,EAAE,OAAO,oBAAoB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACrE;AAEA,UAAM,OAAO,MAAM,SAAS;AAC5B,UAAM,YAAsB,CAAC;AAC7B,UAAM,UAAoB,CAAC;AAC3B,UAAM,SAAmB,CAAC;AAC1B,UAAM,iBAAiB,oBAAI,IAAY;AAEvC,eAAW,YAAY,OAAO;AAC5B,YAAM,MAAM,SAAS,WAAW,SAAS,IAAI,MAAM,SAAS,MAAM,CAAC,IAAI;AACvE,YAAM,QAAQ,KAAK,GAAG;AAEtB,UAAI,CAAC,SAAS,MAAM,MAAM,GAAG;AAC3B,gBAAQ,KAAK,GAAG;AAChB;AAAA,MACF;AAEA,UAAI;AAEF,cAAM,YAAY,cAAc,GAAG;AACnC,cAAMD,IAAG,OAAO,SAAS;AAGzB,uBAAe,IAAIC,MAAK,QAAQ,SAAS,CAAC;AAG1C,eAAO,MAAM;AAEb,kBAAU,KAAK,GAAG;AAAA,MACpB,SAAS,OAAO;AACd,gBAAQ,MAAM,+BAA+B,GAAG,KAAK,KAAK;AAC1D,eAAO,KAAK,GAAG;AAAA,MACjB;AAAA,IACF;AAGA,eAAW,UAAU,gBAAgB;AACnC,YAAM,mBAAmB,MAAM;AAAA,IACjC;AAEA,UAAM,SAAS,IAAI;AAEnB,WAAO,aAAa;AAAA,MAClB,SAAS;AAAA,MACT,WAAW,UAAU;AAAA,MACrB,SAAS,QAAQ;AAAA,MACjB,QAAQ,OAAO;AAAA,IACjB,CAAC;AAAA,EACH,SAAS,OAAO;AACd,YAAQ,MAAM,yBAAyB,KAAK;AAC5C,WAAO,aAAa,EAAE,OAAO,2BAA2B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC5E;AACF;;;ACvoCA,SAAS,YAAYI,WAAU;AAC/B,OAAOC,WAAU;AACjB,OAAOC,YAAW;AAElB,SAAS,UAAAC,eAAc;AAYvB,eAAsB,mBAAmB;AACvC,QAAM,UAAU,IAAI,YAAY;AAEhC,QAAM,SAAS,IAAI,eAAe;AAAA,IAChC,MAAM,MAAM,YAAY;AACtB,YAAM,YAAY,CAAC,SAAiB;AAClC,mBAAW,QAAQ,QAAQ,OAAO,SAAS,KAAK,UAAU,IAAI,CAAC;AAAA;AAAA,CAAM,CAAC;AAAA,MACxE;AAEA,UAAI;AACF,cAAM,OAAO,MAAM,SAAS;AAC5B,cAAM,gBAAgB,OAAO,KAAK,IAAI,EAAE,OAAO,OAAK,CAAC,EAAE,WAAW,GAAG,CAAC,EAAE;AACxE,cAAM,eAAe,IAAI,IAAI,OAAO,KAAK,IAAI,CAAC;AAC9C,cAAM,QAAkB,CAAC;AACzB,cAAM,UAA+C,CAAC;AACtD,cAAM,SAAmB,CAAC;AAC1B,cAAM,gBAA0B,CAAC;AACjC,cAAM,iBAA2B,CAAC;AAGlC,cAAM,WAA8D,CAAC;AAErE,uBAAe,QAAQ,KAAa,eAAuB,IAAmB;AAC5E,cAAI;AACF,kBAAM,UAAU,MAAMC,IAAG,QAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;AAE7D,uBAAW,SAAS,SAAS;AAC3B,kBAAI,MAAM,KAAK,WAAW,GAAG,EAAG;AAEhC,oBAAM,WAAWC,MAAK,KAAK,KAAK,MAAM,IAAI;AAC1C,oBAAM,UAAU,eAAe,GAAG,YAAY,IAAI,MAAM,IAAI,KAAK,MAAM;AAGvE,kBAAI,YAAY,YAAY,QAAQ,WAAW,SAAS,EAAG;AAE3D,kBAAI,MAAM,YAAY,GAAG;AACvB,sBAAM,QAAQ,UAAU,OAAO;AAAA,cACjC,WAAW,YAAY,MAAM,IAAI,GAAG;AAClC,yBAAS,KAAK,EAAE,cAAc,SAAS,SAAS,CAAC;AAAA,cACnD;AAAA,YACF;AAAA,UACF,QAAQ;AAAA,UAER;AAAA,QACF;AAEA,cAAM,YAAY,cAAc;AAChC,cAAM,QAAQ,SAAS;AAEvB,cAAM,QAAQ,SAAS;AACvB,kBAAU,EAAE,MAAM,SAAS,MAAM,CAAC;AAElC,iBAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,cAAI,EAAE,cAAc,SAAS,IAAI,SAAS,CAAC;AAC3C,cAAI,WAAW,MAAM;AAErB,oBAAU;AAAA,YACR,MAAM;AAAA,YACN,SAAS,IAAI;AAAA,YACb;AAAA,YACA,SAAS,KAAK,OAAQ,IAAI,KAAK,QAAS,GAAG;AAAA,YAC3C,aAAa;AAAA,UACf,CAAC;AAGD,cAAI,aAAa,IAAI,QAAQ,GAAG;AAE9B,kBAAM,QAAQ,KAAK,QAAQ;AAC3B,gBAAI,OAAO,MAAM,UAAa,CAAC,OAAO,GAAG;AAGvC,oBAAM,IAAI;AACV,6BAAe,KAAK,QAAQ;AAAA,YAC9B;AAEA;AAAA,UACF;AAGA,cAAI,KAAK,QAAQ,GAAG;AAElB,kBAAM,MAAMA,MAAK,QAAQ,YAAY;AACrC,kBAAM,WAAW,aAAa,MAAM,GAAG,CAAC,IAAI,MAAM;AAClD,gBAAI,UAAU;AACd,gBAAI,SAAS,IAAI,QAAQ,IAAI,OAAO,GAAG,GAAG;AAE1C,mBAAO,KAAK,MAAM,GAAG;AACnB;AACA,uBAAS,IAAI,QAAQ,IAAI,OAAO,GAAG,GAAG;AAAA,YACxC;AAGA,kBAAM,kBAAkB,GAAG,QAAQ,IAAI,OAAO,GAAG,GAAG;AACpD,kBAAM,cAAc,cAAc,eAAe;AAEjD,gBAAI;AACF,oBAAMD,IAAG,OAAO,UAAU,WAAW;AACrC,sBAAQ,KAAK,EAAE,MAAM,cAAc,IAAI,gBAAgB,CAAC;AACxD,6BAAe;AACf,yBAAW;AACX,yBAAW;AAAA,YACb,SAAS,KAAK;AACZ,sBAAQ,MAAM,oBAAoB,YAAY,KAAK,GAAG;AACtD,qBAAO,KAAK,oBAAoB,YAAY,EAAE;AAC9C;AAAA,YACF;AAAA,UACF;AAEA,cAAI;AACF,kBAAM,UAAU,YAAY,YAAY;AAExC,gBAAI,SAAS;AAEX,oBAAM,MAAMC,MAAK,QAAQ,YAAY,EAAE,YAAY;AAEnD,kBAAI,QAAQ,QAAQ;AAElB,qBAAK,QAAQ,IAAI,EAAE,GAAG,EAAE,GAAG,GAAG,GAAG,EAAE,GAAG,GAAG,GAAG;AAAA,cAC9C,OAAO;AACL,oBAAI;AACF,wBAAM,SAAS,MAAMD,IAAG,SAAS,QAAQ;AACzC,wBAAM,WAAW,MAAME,OAAM,MAAM,EAAE,SAAS;AAG9C,wBAAM,EAAE,MAAM,KAAK,IAAI,MAAMA,OAAM,MAAM,EACtC,OAAO,IAAI,IAAI,EAAE,KAAK,SAAS,CAAC,EAChC,YAAY,EACZ,IAAI,EACJ,SAAS,EAAE,mBAAmB,KAAK,CAAC;AAEvC,wBAAM,WAAWC,QAAO,IAAI,kBAAkB,IAAI,GAAG,KAAK,OAAO,KAAK,QAAQ,GAAG,CAAC;AAElF,uBAAK,QAAQ,IAAI;AAAA,oBACf,GAAG,EAAE,GAAG,SAAS,SAAS,GAAG,GAAG,SAAS,UAAU,EAAE;AAAA,oBACrD,GAAG;AAAA,kBACL;AAAA,gBACF,QAAQ;AAEN,uBAAK,QAAQ,IAAI,EAAE,GAAG,EAAE,GAAG,GAAG,GAAG,EAAE,EAAE;AAAA,gBACvC;AAAA,cACF;AAAA,YACF,OAAO;AAEL,mBAAK,QAAQ,IAAI,CAAC;AAAA,YACpB;AAEA,yBAAa,IAAI,QAAQ;AACzB,kBAAM,KAAK,QAAQ;AAAA,UACrB,SAAS,OAAO;AACd,oBAAQ,MAAM,qBAAqB,YAAY,KAAK,KAAK;AACzD,mBAAO,KAAK,YAAY;AAAA,UAC1B;AAAA,QACF;AAGA,kBAAU,EAAE,MAAM,WAAW,SAAS,sCAAsC,CAAC;AAG7E,cAAM,qBAAqB,oBAAI,IAAY;AAC3C,cAAM,cAAc,eAAe,IAAI;AACvC,mBAAW,CAAC,UAAU,KAAK,KAAK,aAAa;AAE3C,cAAI,MAAM,MAAM,UAAa,YAAY,KAAK,GAAG;AAC/C,uBAAW,aAAa,qBAAqB,QAAQ,GAAG;AACtD,iCAAmB,IAAI,SAAS;AAAA,YAClC;AAAA,UACF;AAAA,QACF;AAGA,uBAAe,YAAY,KAAa,eAAuB,IAAmB;AAChF,cAAI;AACF,kBAAM,UAAU,MAAMH,IAAG,QAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;AAE7D,uBAAW,SAAS,SAAS;AAC3B,kBAAI,MAAM,KAAK,WAAW,GAAG,EAAG;AAEhC,oBAAM,WAAWC,MAAK,KAAK,KAAK,MAAM,IAAI;AAC1C,oBAAM,UAAU,eAAe,GAAG,YAAY,IAAI,MAAM,IAAI,KAAK,MAAM;AAEvE,kBAAI,MAAM,YAAY,GAAG;AACvB,sBAAM,YAAY,UAAU,OAAO;AAAA,cACrC,WAAW,YAAY,MAAM,IAAI,GAAG;AAClC,sBAAM,aAAa,WAAW,OAAO;AACrC,oBAAI,CAAC,mBAAmB,IAAI,UAAU,GAAG;AACvC,gCAAc,KAAK,UAAU;AAAA,gBAC/B;AAAA,cACF;AAAA,YACF;AAAA,UACF,QAAQ;AAAA,UAER;AAAA,QACF;AAEA,cAAM,YAAY,cAAc,QAAQ;AACxC,YAAI;AACF,gBAAM,YAAY,SAAS;AAAA,QAC7B,QAAQ;AAAA,QAER;AAGA,kBAAU,EAAE,MAAM,WAAW,SAAS,+BAA+B,CAAC;AAEtE,uBAAe,kBAAkB,KAA4B;AAC3D,cAAI;AACF,kBAAM,UAAU,MAAMD,IAAG,QAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;AAE7D,uBAAW,SAAS,SAAS;AAC3B,kBAAI,MAAM,KAAK,WAAW,GAAG,EAAG;AAChC,kBAAI,CAAC,MAAM,YAAY,EAAG;AAG1B,oBAAM,WAAWC,MAAK,KAAK,KAAK,MAAM,IAAI;AAC1C,kBAAI,aAAa,UAAW;AAG5B,oBAAM,kBAAkB,QAAQ;AAGhC,oBAAM,mBAAmB,QAAQ;AAAA,YACnC;AAAA,UACF,QAAQ;AAAA,UAER;AAAA,QACF;AAEA,cAAM,kBAAkB,cAAc,CAAC;AAGvC,YAAI;AACF,gBAAM,6BAA6B,SAAS;AAAA,QAC9C,QAAQ;AAAA,QAER;AAGA,kBAAU,EAAE,MAAM,WAAW,SAAS,mCAAmC,CAAC;AAC1E,cAAM,kBAA4B,CAAC;AACnC,cAAM,UAAW,KAAK,SAAS,CAAC;AAChC,cAAM,eAAe,QAAQ,IAAI,4BAA4B,IAAI,QAAQ,OAAO,EAAE;AAElF,mBAAW,OAAO,OAAO,KAAK,IAAI,GAAG;AACnC,cAAI,IAAI,WAAW,GAAG,EAAG;AAEzB,gBAAM,QAAQ,KAAK,GAAG;AACtB,cAAI,CAAC,MAAO;AAGZ,cAAI,MAAM,MAAM,QAAW;AAEzB,gBAAI,MAAM,MAAM,GAAG;AACjB,oBAAMG,aAAY,cAAc,GAAG;AACnC,kBAAI;AACF,sBAAMJ,IAAG,OAAOI,UAAS;AAAA,cAC3B,QAAQ;AAEN,uBAAO,MAAM;AAAA,cACf;AAAA,YACF;AACA;AAAA,UACF;AAGA,gBAAM,YAAY,cAAc,GAAG;AACnC,cAAI;AACF,kBAAMJ,IAAG,OAAO,SAAS;AAAA,UAC3B,QAAQ;AAEN,4BAAgB,KAAK,GAAG;AACxB,mBAAO,KAAK,GAAG;AAAA,UACjB;AAAA,QACF;AAEA,YAAI,gBAAgB,SAAS,GAAG;AAC9B,oBAAU,EAAE,MAAM,WAAW,SAAS,WAAW,gBAAgB,MAAM,uBAAuB,CAAC;AAAA,QACjG;AAEA,cAAM,SAAS,IAAI;AAEnB,kBAAU;AAAA,UACR,MAAM;AAAA,UACN;AAAA,UACA,OAAO,MAAM;AAAA,UACb,SAAS,QAAQ;AAAA,UACjB,QAAQ,OAAO;AAAA,UACf,cAAc;AAAA,UACd,eAAe,cAAc,SAAS,IAAI,gBAAgB;AAAA,UAC1D,gBAAgB,eAAe;AAAA,UAC/B,iBAAiB,gBAAgB;AAAA,QACnC,CAAC;AAAA,MACH,SAAS,OAAO;AACd,gBAAQ,MAAM,gBAAgB,KAAK;AACnC,kBAAU,EAAE,MAAM,SAAS,SAAS,cAAc,CAAC;AAAA,MACrD,UAAE;AACA,mBAAW,MAAM;AAAA,MACnB;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO,IAAI,SAAS,QAAQ;AAAA,IAC1B,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,MACjB,cAAc;AAAA,IAChB;AAAA,EACF,CAAC;AACH;AAKA,eAAsB,oBAAoB,SAAkB;AAC1D,MAAI;AACF,UAAM,EAAE,MAAM,IAAI,MAAM,QAAQ,KAAK;AAErC,QAAI,CAAC,SAAS,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,GAAG;AACzD,aAAO,aAAa,EAAE,OAAO,oBAAoB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACrE;AAEA,UAAM,UAAoB,CAAC;AAC3B,UAAM,SAAmB,CAAC;AAE1B,eAAW,cAAc,OAAO;AAE9B,UAAI,CAAC,WAAW,WAAW,UAAU,GAAG;AACtC,eAAO,KAAK,iBAAiB,UAAU,EAAE;AACzC;AAAA,MACF;AAEA,YAAM,WAAW,cAAc,UAAU;AAEzC,UAAI;AACF,cAAMA,IAAG,OAAO,QAAQ;AACxB,gBAAQ,KAAK,UAAU;AAAA,MACzB,SAAS,KAAK;AACZ,gBAAQ,MAAM,oBAAoB,UAAU,KAAK,GAAG;AACpD,eAAO,KAAK,UAAU;AAAA,MACxB;AAAA,IACF;AAGA,UAAM,YAAY,cAAc,QAAQ;AAExC,QAAI;AACF,YAAM,6BAA6B,SAAS;AAAA,IAC9C,QAAQ;AAAA,IAER;AAEA,WAAO,aAAa;AAAA,MAClB,SAAS;AAAA,MACT,SAAS,QAAQ;AAAA,MACjB,QAAQ,OAAO;AAAA,IACjB,CAAC;AAAA,EACH,SAAS,OAAO;AACd,YAAQ,MAAM,6BAA6B,KAAK;AAChD,WAAO,aAAa,EAAE,OAAO,kCAAkC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACnF;AACF;;;ACvXA,OAAOK,YAAW;AAClB,SAAS,UAAAC,eAAc;AAavB,SAAS,cAAc,KAA6C;AAClE,QAAM,SAAS,IAAI,IAAI,GAAG;AAE1B,QAAM,OAAO,GAAG,OAAO,QAAQ,KAAK,OAAO,IAAI;AAE/C,QAAMC,SAAO,OAAO;AACpB,SAAO,EAAE,MAAM,MAAAA,OAAK;AACtB;AAKA,eAAe,mBAAmB,KAAoD;AACpF,QAAM,WAAW,MAAM,MAAM,GAAG;AAChC,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI,MAAM,oBAAoB,SAAS,MAAM,EAAE;AAAA,EACvD;AAEA,QAAM,SAAS,OAAO,KAAK,MAAM,SAAS,YAAY,CAAC;AAEvD,QAAM,WAAW,MAAMC,OAAM,MAAM,EAAE,SAAS;AAG9C,QAAM,EAAE,MAAM,KAAK,IAAI,MAAMA,OAAM,MAAM,EACtC,OAAO,IAAI,IAAI,EAAE,KAAK,SAAS,CAAC,EAChC,YAAY,EACZ,IAAI,EACJ,SAAS,EAAE,mBAAmB,KAAK,CAAC;AAEvC,QAAM,WAAWC,QAAO,IAAI,kBAAkB,IAAI,GAAG,KAAK,OAAO,KAAK,QAAQ,GAAG,CAAC;AAElF,SAAO;AAAA,IACL,GAAG,EAAE,GAAG,SAAS,SAAS,GAAG,GAAG,SAAS,UAAU,EAAE;AAAA,IACrD,GAAG;AAAA,EACL;AACF;AAKA,eAAsB,iBAAiB,SAAkB;AACvD,QAAM,UAAU,IAAI,YAAY;AAEhC,QAAM,SAAS,IAAI,eAAe;AAAA,IAChC,MAAM,MAAM,YAAY;AACtB,YAAM,YAAY,CAAC,SAAiB;AAClC,mBAAW,QAAQ,QAAQ,OAAO,SAAS,KAAK,UAAU,IAAI,CAAC;AAAA;AAAA,CAAM,CAAC;AAAA,MACxE;AAEA,UAAI;AACF,cAAM,EAAE,KAAK,IAAI,MAAM,QAAQ,KAAK;AAEpC,YAAI,CAAC,QAAQ,CAAC,MAAM,QAAQ,IAAI,KAAK,KAAK,WAAW,GAAG;AACtD,oBAAU,EAAE,MAAM,SAAS,SAAS,mBAAmB,CAAC;AACxD,qBAAW,MAAM;AACjB;AAAA,QACF;AAEA,cAAM,OAAO,MAAM,SAAS;AAC5B,cAAM,QAAkB,CAAC;AACzB,cAAM,UAAoB,CAAC;AAC3B,cAAM,SAAmB,CAAC;AAE1B,cAAM,QAAQ,KAAK;AACnB,kBAAU,EAAE,MAAM,SAAS,MAAM,CAAC;AAElC,iBAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,gBAAM,MAAM,KAAK,CAAC,EAAE,KAAK;AACzB,cAAI,CAAC,IAAK;AAEV,oBAAU;AAAA,YACR,MAAM;AAAA,YACN,SAAS,IAAI;AAAA,YACb;AAAA,YACA,SAAS,KAAK,OAAQ,IAAI,KAAK,QAAS,GAAG;AAAA,YAC3C,aAAa;AAAA,UACf,CAAC;AAED,cAAI;AAEF,kBAAM,EAAE,MAAM,MAAAF,OAAK,IAAI,cAAc,GAAG;AAGxC,kBAAM,gBAAgB,aAAa,MAAMA,MAAI;AAC7C,gBAAI,eAAe;AACjB,sBAAQ,KAAKA,MAAI;AACjB;AAAA,YACF;AAGA,kBAAM,WAAW,iBAAiB,MAAM,IAAI;AAG5C,kBAAM,YAAY,MAAM,mBAAmB,GAAG;AAI9C,yBAAa,MAAMA,QAAM;AAAA,cACvB,GAAG,UAAU;AAAA,cACb,GAAG,UAAU;AAAA,cACb,GAAG;AAAA,YACL,CAAC;AAED,kBAAM,KAAKA,MAAI;AAAA,UACjB,SAAS,OAAO;AACd,oBAAQ,MAAM,oBAAoB,GAAG,KAAK,KAAK;AAC/C,mBAAO,KAAK,GAAG;AAAA,UACjB;AAAA,QACF;AAEA,cAAM,SAAS,IAAI;AAEnB,kBAAU;AAAA,UACR,MAAM;AAAA,UACN,OAAO,MAAM;AAAA,UACb,SAAS,QAAQ;AAAA,UACjB,QAAQ,OAAO;AAAA,QACjB,CAAC;AAAA,MACH,SAAS,OAAO;AACd,gBAAQ,MAAM,kBAAkB,KAAK;AACrC,kBAAU,EAAE,MAAM,SAAS,SAAS,gBAAgB,CAAC;AAAA,MACvD,UAAE;AACA,mBAAW,MAAM;AAAA,MACnB;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO,IAAI,SAAS,QAAQ;AAAA,IAC1B,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,MACjB,cAAc;AAAA,IAChB;AAAA,EACF,CAAC;AACH;AAKA,eAAsB,gBAAgB;AACpC,MAAI;AACF,UAAM,OAAO,MAAM,SAAS;AAC5B,UAAM,OAAO,KAAK,SAAS,CAAC;AAE5B,WAAO,SAAS,KAAK,EAAE,KAAK,CAAC;AAAA,EAC/B,SAAS,OAAO;AACd,YAAQ,MAAM,uBAAuB,KAAK;AAC1C,WAAO,SAAS,KAAK,EAAE,OAAO,qBAAqB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACvE;AACF;AAKA,eAAsB,iBAAiB,SAAkB;AACvD,MAAI;AACF,UAAM,EAAE,KAAK,IAAI,MAAM,QAAQ,KAAK;AAEpC,QAAI,CAAC,MAAM,QAAQ,IAAI,GAAG;AACxB,aAAO,SAAS,KAAK,EAAE,OAAO,oBAAoB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACtE;AAEA,UAAM,OAAO,MAAM,SAAS;AAG5B,SAAK,QAAQ,KAAK,IAAI,SAAO,IAAI,QAAQ,OAAO,EAAE,CAAC;AAEnD,UAAM,SAAS,IAAI;AAEnB,WAAO,SAAS,KAAK,EAAE,SAAS,MAAM,MAAM,KAAK,MAAM,CAAC;AAAA,EAC1D,SAAS,OAAO;AACd,YAAQ,MAAM,0BAA0B,KAAK;AAC7C,WAAO,SAAS,KAAK,EAAE,OAAO,wBAAwB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC1E;AACF;;;AC5LA,OAAOG,YAAW;AAClB,OAAOC,WAAU;AACjB,OAAOC,SAAQ;AAef,IAAM,kBAAkB;AAAA,EACtB,EAAE,MAAM,eAAe,MAAM,GAAG;AAAA,EAChC,EAAE,MAAM,YAAY,MAAM,GAAG;AAAA,EAC7B,EAAE,MAAM,kBAAkB,MAAM,IAAI;AACtC;AAEA,eAAsB,sBAAsB,SAAkB;AAC5D,QAAM,UAAU,IAAI,YAAY;AAEhC,MAAI;AACJ,MAAI;AACF,UAAM,OAAO,MAAM,QAAQ,KAAK;AAChC,gBAAY,KAAK;AAEjB,QAAI,CAAC,WAAW;AACd,aAAO,aAAa,EAAE,OAAO,yBAAyB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC1E;AAAA,EACF,QAAQ;AACN,WAAO,aAAa,EAAE,OAAO,uBAAuB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACxE;AAGA,QAAM,WAAWC,MAAK,SAAS,SAAS,EAAE,YAAY;AACtD,MAAI,aAAa,iBAAiB,aAAa,eAAe;AAC5D,WAAO,aAAa;AAAA,MAClB,OAAO;AAAA,IACT,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACpB;AAGA,QAAM,aAAa,cAAc,UAAU,QAAQ,OAAO,EAAE,CAAC;AAG7D,MAAI;AACF,UAAMC,IAAG,OAAO,UAAU;AAAA,EAC5B,QAAQ;AACN,WAAO,aAAa,EAAE,OAAO,wBAAwB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACzE;AAGA,MAAI;AACJ,MAAI;AACF,eAAW,MAAMC,OAAM,UAAU,EAAE,SAAS;AAAA,EAC9C,QAAQ;AACN,WAAO,aAAa,EAAE,OAAO,mCAAmC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACpF;AAGA,QAAM,YAAY,cAAc;AAGhC,MAAI;AACF,UAAMD,IAAG,OAAO,SAAS;AAAA,EAC3B,QAAQ;AACN,WAAO,aAAa;AAAA,MAClB,OAAO;AAAA,IACT,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACpB;AAEA,QAAM,SAAS,IAAI,eAAe;AAAA,IAChC,MAAM,MAAM,YAAY;AACtB,YAAM,YAAY,CAAC,SAAiB;AAClC,mBAAW,QAAQ,QAAQ,OAAO,SAAS,KAAK,UAAU,IAAI,CAAC;AAAA;AAAA,CAAM,CAAC;AAAA,MACxE;AAEA,UAAI;AACF,cAAM,QAAQ,gBAAgB;AAC9B,cAAM,YAAsB,CAAC;AAC7B,cAAM,SAAmB,CAAC;AAE1B,kBAAU;AAAA,UACR,MAAM;AAAA,UACN;AAAA,UACA,YAAY,GAAG,SAAS,KAAK,IAAI,SAAS,MAAM;AAAA,QAClD,CAAC;AAED,iBAAS,IAAI,GAAG,IAAI,gBAAgB,QAAQ,KAAK;AAC/C,gBAAM,SAAS,gBAAgB,CAAC;AAEhC,oBAAU;AAAA,YACR,MAAM;AAAA,YACN,SAAS,IAAI;AAAA,YACb;AAAA,YACA,SAAS,KAAK,OAAQ,IAAI,KAAK,QAAS,GAAG;AAAA,YAC3C,SAAS,cAAc,OAAO,IAAI,KAAK,OAAO,IAAI,IAAI,OAAO,IAAI;AAAA,UACnE,CAAC;AAED,cAAI;AACF,kBAAM,aAAaD,MAAK,KAAK,WAAW,OAAO,IAAI;AAEnD,kBAAME,OAAM,UAAU,EACnB,OAAO,OAAO,MAAM,OAAO,MAAM;AAAA,cAChC,KAAK;AAAA,cACL,UAAU;AAAA,YACZ,CAAC,EACA,IAAI,EAAE,SAAS,IAAI,CAAC,EACpB,OAAO,UAAU;AAEpB,sBAAU,KAAK,OAAO,IAAI;AAAA,UAC5B,SAAS,OAAO;AACd,oBAAQ,MAAM,sBAAsB,OAAO,IAAI,KAAK,KAAK;AACzD,mBAAO,KAAK,OAAO,IAAI;AAAA,UACzB;AAAA,QACF;AAGA,YAAI,UAAU,aAAa,UAAU,MAAM,WAAW,UAAU,WAAW,IAAI,MAAM,EAAE;AACvF,YAAI,OAAO,SAAS,GAAG;AACrB,qBAAW,IAAI,OAAO,MAAM;AAAA,QAC9B;AAEA,kBAAU;AAAA,UACR,MAAM;AAAA,UACN,WAAW,UAAU;AAAA,UACrB,QAAQ,OAAO;AAAA,UACf;AAAA,QACF,CAAC;AAED,mBAAW,MAAM;AAAA,MACnB,SAAS,OAAO;AACd,gBAAQ,MAAM,6BAA6B,KAAK;AAChD,kBAAU,EAAE,MAAM,SAAS,SAAS,8BAA8B,CAAC;AACnE,mBAAW,MAAM;AAAA,MACnB;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO,IAAI,SAAS,QAAQ;AAAA,IAC1B,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,MACjB,YAAY;AAAA,IACd;AAAA,EACF,CAAC;AACH;;;AdvIA,IAAM,aAAa,cAAc,YAAY,GAAG;AAChD,IAAM,YAAY,QAAQ,UAAU;AAWpC,SAAS,gBAAgB,MAAgC;AACvD,SAAO,IAAI,QAAQ,CAACC,aAAY;AAC9B,UAAM,SAAS,aAAa;AAC5B,WAAO,KAAK,SAAS,MAAM;AACzB,MAAAA,SAAQ,KAAK;AAAA,IACf,CAAC;AACD,WAAO,KAAK,aAAa,MAAM;AAC7B,aAAO,MAAM;AACb,MAAAA,SAAQ,IAAI;AAAA,IACd,CAAC;AACD,WAAO,OAAO,IAAI;AAAA,EACpB,CAAC;AACH;AAKA,eAAe,kBAAkB,WAAmB,cAAc,IAAqB;AACrF,WAAS,IAAI,GAAG,IAAI,aAAa,KAAK;AACpC,UAAM,OAAO,YAAY;AACzB,QAAI,MAAM,gBAAgB,IAAI,GAAG;AAC/B,aAAO;AAAA,IACT;AAAA,EACF;AACA,QAAM,IAAI,MAAM,mCAAmC,SAAS,QAAQ,YAAY,cAAc,CAAC,EAAE;AACnG;AAEA,eAAsB,YAAY,SAAwB;AACxD,QAAM,EAAE,MAAM,eAAe,WAAW,KAAK,IAAI;AAGjD,QAAM,OAAO,MAAM,kBAAkB,aAAa;AAClD,MAAI,SAAS,eAAe;AAC1B,YAAQ,IAAI,QAAQ,aAAa,0BAA0B,IAAI,UAAU;AAAA,EAC3E;AAEA,QAAM,MAAM,QAAQ;AAGpB,UAAQ,IAAI,mBAAmB;AAI/B,QAAM,gBAAgB,KAAK,WAAW,aAAa;AAEnD,MAAI,WAAW,aAAa,GAAG;AAC7B,YAAQ,EAAE,MAAM,eAAe,OAAO,KAAK,CAAC;AAAA,EAC9C;AAGA,MAAI,IAAI,CAAC,KAAK,KAAK,SAAS;AAC1B,QAAI,IAAI,SAAS,sBAAsB;AACrC,WAAK;AAAA,IACP,OAAO;AACL,cAAQ,KAAK,EAAE,OAAO,OAAO,CAAC,EAAE,KAAK,KAAK,IAAI;AAAA,IAChD;AAAA,EACF,CAAC;AACD,MAAI,IAAI,CAAC,KAAK,KAAK,SAAS;AAC1B,QAAI,IAAI,SAAS,sBAAsB;AACrC,WAAK;AAAA,IACP,OAAO;AACL,cAAQ,WAAW,EAAE,UAAU,MAAM,OAAO,OAAO,CAAC,EAAE,KAAK,KAAK,IAAI;AAAA,IACtE;AAAA,EACF,CAAC;AAGD,MAAI,IAAI,oBAAoB,YAAY,UAAU,CAAC;AACnD,MAAI,IAAI,4BAA4B,YAAY,iBAAiB,CAAC;AAClE,MAAI,IAAI,sBAAsB,YAAY,YAAY,CAAC;AACvD,MAAI,IAAI,4BAA4B,YAAY,iBAAiB,CAAC;AAClE,MAAI,IAAI,6BAA6B,YAAY,kBAAkB,CAAC;AACpE,MAAI,IAAI,oBAAoB,YAAY,aAAa,CAAC;AAItD,MAAI,KAAK,sBAAsB,eAAe,YAAY,CAAC;AAC3D,MAAI,KAAK,6BAA6B,YAAY,kBAAkB,CAAC;AACrE,MAAI,KAAK,sBAAsB,YAAY,YAAY,CAAC;AACxD,MAAI,KAAK,oBAAoB,YAAY,kBAAkB,IAAI,CAAC;AAChE,MAAI,KAAK,oBAAoB,YAAY,YAAY,IAAI,CAAC;AAC1D,MAAI,KAAK,gCAAgC,YAAY,uBAAuB,IAAI,CAAC;AACjF,MAAI,KAAK,gCAAgC,YAAY,uBAAuB,IAAI,CAAC;AACjF,MAAI,KAAK,+BAA+B,YAAY,sBAAsB,IAAI,CAAC;AAC/E,MAAI,KAAK,mCAAmC,YAAY,yBAAyB,IAAI,CAAC;AACtF,MAAI,KAAK,8BAA8B,YAAY,mBAAmB,CAAC;AACvE,MAAI,KAAK,oBAAoB,YAAY,kBAAkB,IAAI,CAAC;AAChE,MAAI,KAAK,8BAA8B,YAAY,mBAAmB,CAAC;AACvE,MAAI,KAAK,sBAAsB,YAAY,kBAAkB,IAAI,CAAC;AAClE,MAAI,KAAK,oBAAoB,YAAY,gBAAgB,CAAC;AAC1D,MAAI,KAAK,gCAAgC,YAAY,uBAAuB,IAAI,CAAC;AAGjF,MAAI,KAAK,sBAAsB,YAAY,YAAY,CAAC;AAIxD,MAAI,IAAI,QAAQ,OAAO,KAAK,WAAW,QAAQ,CAAC,CAAC;AAGjD,QAAM,YAAY,QAAQ,WAAW,WAAW;AAGhD,MAAI,IAAI,KAAK,CAAC,KAAc,QAAkB;AAC5C,UAAM,WAAW,KAAK,WAAW,YAAY;AAC7C,QAAI,WAAW,QAAQ,GAAG;AACxB,UAAI,OAAO,aAAa,UAAU,OAAO;AAEzC,YAAM,UAAU,QAAQ,IAAI,uBAAuB;AACnD,YAAM,SAAS;AAAA,wCACmB,KAAK,UAAU,SAAS,CAAC;AAAA,uCAC1B,KAAK,UAAU,OAAO,CAAC;AAAA;AAExD,aAAO,KAAK,QAAQ,WAAW,GAAG,MAAM,SAAS;AACjD,UAAI,KAAK,MAAM,EAAE,KAAK,IAAI;AAAA,IAC5B,OAAO;AACL,UAAI,OAAO,GAAG,EAAE,KAAK,4CAA4C;AAAA,IACnE;AAAA,EACF,CAAC;AAGD,MAAI,IAAI,QAAQ,OAAO,SAAS,CAAC;AAGjC,MAAI,OAAO,MAAM,MAAM;AACrB,YAAQ,IAAI;AAAA;AAAA;AAAA;AAAA,qBAIA,UAAU,SAAS,KAAK,QAAQ,UAAU,MAAM,GAAG,IAAI,UAAU,OAAO,EAAE,CAAC;AAAA,gCAChE,IAAI;AAAA;AAAA,CAE9B;AAEG,QAAI,MAAM;AACR,aAAO,MAAM,EAAE,KAAK,CAAC,QAAQ;AAC3B,YAAI,QAAQ,oBAAoB,IAAI,EAAE;AAAA,MACxC,CAAC,EAAE,MAAM,MAAM;AAAA,MAEf,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AACH;AAGA,SAAS,YAEP,SACA,YAAY,OACZ;AACA,SAAO,OAAO,KAAc,QAAkB;AAC5C,QAAI;AACF,YAAM,UAAU,mBAAmB,GAAG;AACtC,YAAM,WAAW,MAAM,QAAQ,OAAO;AACtC,UAAI,WAAW;AACb,cAAM,sBAAsB,KAAK,QAAQ;AAAA,MAC3C,OAAO;AACL,cAAM,aAAa,KAAK,QAAQ;AAAA,MAClC;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,MAAM,kBAAkB,KAAK;AACrC,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,wBAAwB,CAAC;AAAA,IACzD;AAAA,EACF;AACF;AAGA,SAAS,eAEP,SACA;AACA,SAAO,OAAO,KAAc,QAAkB;AAC5C,QAAI;AACF,YAAM,UAAU,MAAM,sBAAsB,GAAG;AAC/C,YAAM,WAAW,MAAM,QAAQ,OAAO;AACtC,YAAM,aAAa,KAAK,QAAQ;AAAA,IAClC,SAAS,OAAO;AACd,cAAQ,MAAM,kBAAkB,KAAK;AACrC,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,wBAAwB,CAAC;AAAA,IACzD;AAAA,EACF;AACF;AAGA,SAAS,mBAAmB,KAAkC;AAC5D,QAAM,MAAM,IAAI,IAAI,IAAI,KAAK,UAAU,IAAI,QAAQ,IAAI,EAAE;AAEzD,QAAM,UAAU,IAAI,QAAQ;AAC5B,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,OAAO,GAAG;AACtD,QAAI,OAAO;AACT,UAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,cAAM,QAAQ,CAAC,MAAM,QAAQ,OAAO,KAAK,CAAC,CAAC;AAAA,MAC7C,OAAO;AACL,gBAAQ,IAAI,KAAK,KAAK;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AAEA,QAAM,OAAoB;AAAA,IACxB,QAAQ,IAAI;AAAA,IACZ;AAAA,EACF;AAGA,MAAI,IAAI,WAAW,SAAS,IAAI,WAAW,QAAQ;AACjD,QAAI,IAAI,MAAM;AACZ,WAAK,OAAO,KAAK,UAAU,IAAI,IAAI;AAAA,IACrC;AAAA,EACF;AAEA,SAAO,IAAI,WAAW,QAAQ,IAAI,SAAS,GAAG,IAAI;AACpD;AAGA,eAAe,sBAAsB,KAA2C;AAC9E,QAAM,MAAM,IAAI,IAAI,IAAI,KAAK,UAAU,IAAI,QAAQ,IAAI,EAAE;AAEzD,QAAM,UAAU,IAAI,QAAQ;AAC5B,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,OAAO,GAAG;AACtD,QAAI,OAAO;AACT,UAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,cAAM,QAAQ,CAAC,MAAM,QAAQ,OAAO,KAAK,CAAC,CAAC;AAAA,MAC7C,OAAO;AACL,gBAAQ,IAAI,KAAK,KAAK;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AAGA,QAAM,SAAmB,CAAC;AAC1B,mBAAiB,SAAS,KAAK;AAC7B,WAAO,KAAK,OAAO,KAAK,KAAK,CAAC;AAAA,EAChC;AACA,QAAM,OAAO,OAAO,OAAO,MAAM;AAEjC,QAAM,OAAoB;AAAA,IACxB,QAAQ,IAAI;AAAA,IACZ;AAAA,IACA;AAAA,EACF;AAEA,SAAO,IAAI,WAAW,QAAQ,IAAI,SAAS,GAAG,IAAI;AACpD;AAGA,eAAe,aAAa,KAAe,UAA+B;AACxE,MAAI,OAAO,SAAS,MAAM;AAG1B,WAAS,QAAQ,QAAQ,CAAC,OAAO,QAAQ;AACvC,QAAI,UAAU,KAAK,KAAK;AAAA,EAC1B,CAAC;AAGD,QAAM,OAAO,MAAM,SAAS,KAAK;AACjC,MAAI,KAAK,IAAI;AACf;AAGA,eAAe,sBAAsB,KAAe,UAA+B;AACjF,MAAI,OAAO,SAAS,MAAM;AAG1B,WAAS,QAAQ,QAAQ,CAAC,OAAO,QAAQ;AACvC,QAAI,UAAU,KAAK,KAAK;AAAA,EAC1B,CAAC;AAGD,MAAI,SAAS,MAAM;AACjB,UAAM,SAAS,SAAS,KAAK,UAAU;AACvC,UAAM,UAAU,IAAI,YAAY;AAEhC,QAAI;AACF,aAAO,MAAM;AACX,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,YAAI,KAAM;AACV,YAAI,MAAM,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC,CAAC;AAAA,MACnD;AACA,UAAI,IAAI;AAAA,IACV,SAAS,OAAO;AACd,cAAQ,MAAM,oBAAoB,KAAK;AACvC,UAAI,IAAI;AAAA,IACV;AAAA,EACF,OAAO;AACL,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,QAAI,KAAK,IAAI;AAAA,EACf;AACF;","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","path","fs","sharp","fs","path","S3Client","PutObjectCommand","DeleteObjectCommand","S3Client","fs","PutObjectCommand","path","fs","path","fs","path","S3Client","DeleteObjectCommand","PutObjectCommand","fs","path","sharp","encode","fs","path","sharp","encode","localPath","sharp","encode","path","sharp","encode","sharp","path","fs","path","fs","sharp","resolve"]}
|