import fs from "node:fs/promises"; import { posix as path } from "node:path"; import { fsCachePath } from "../../utils/runtimeChecks.js"; const copied = new Set(); const dirsCreated = new Set(); export async function saveAndCopyAsset( hash, image, buffer, outDir, assetsDir, assetPath, isSsrBuild ) { // Use Set for O(1) lookup instead of O(n) array search if (copied.has(assetPath)) return; const src = fsCachePath + hash; const dest = path.join(outDir, isSsrBuild ? "/client" : "", assetPath); const destDir = path.dirname(dest); // Check if destination already exists and is valid (skip copy) try { const stats = await fs.stat(dest); if (stats.size > 0) { copied.add(assetPath); console.log(`[skip] ${assetPath} (already exists)`); return; } } catch { // File doesn't exist, continue with copy } // Ensure destination directory exists (but only create once per unique dir) if (!dirsCreated.has(destDir)) { await fs.mkdir(destDir, { recursive: true }); dirsCreated.add(destDir); } // Try hardlink first (instant, no disk usage), fallback to copy let linked = false; try { await fs.link(src, dest); linked = true; } catch (linkError) { // Hardlink failed, try copy try { await fs.copyFile(src, dest); } catch (copyError) { if (copyError.code === "ENOENT") { // Source doesn't exist, regenerate from buffer/image const imageBuffer = buffer || (await image.toBuffer()); await Promise.all([ fs.writeFile(src, imageBuffer), fs.writeFile(dest, imageBuffer) ]); console.log(`[regen] ${src} -> ${dest}`); } else { throw copyError; } } } copied.add(assetPath); } // Helper to pre-create directories in batch (call before processing) export async function preCreateDirectories(assetPaths, outDir, isSsrBuild) { const uniqueDirs = new Set( assetPaths.map(assetPath => path.dirname(path.join(outDir, isSsrBuild ? "/client" : "", assetPath)) ) ); await Promise.all( [...uniqueDirs].map(dir => fs.mkdir(dir, { recursive: true })) ); uniqueDirs.forEach(dir => dirsCreated.add(dir)); }