import { MODEL_EXTENSIONS } from './constants' import type { FileChange, PushFile } from './types' interface DiffResult { fileChanges: Map changedFilesToPush: PushFile[] deletedFileNames: string[] deletePaths: string[] } /** * Classify each file as new, changed, or unchanged by comparing against * the remote file map. * * Rules: * - Models: always re-pushed, * but marked as 'unchanged' in the commit message when the folder already * exists (we keep the current behavior of always delivering the model file). * - Textures: compared by size (not compressed, reliable). * - Orphan remote files: classified as deletions. */ export function classifyFileChanges( filesToPush: PushFile[], remoteFileMap: Map, folderPath: string, ): DiffResult { const fileChanges = new Map() const changedFilesToPush: PushFile[] = [] for (const f of filesToPush) { const filename = f.path.split('/').pop() ?? '' const ext = filename.slice(filename.lastIndexOf('.')).toLowerCase() const isModel = MODEL_EXTENSIONS.has(ext) if (isModel) { const remoteSize = remoteFileMap.get(filename.toLowerCase()) fileChanges.set(filename.toLowerCase(), remoteSize === undefined ? 'new' : 'unchanged') changedFilesToPush.push(f) } else { const localSize = Buffer.from(f.contentBase64, 'base64').length const remoteSize = remoteFileMap.get(filename.toLowerCase()) if (remoteSize === undefined) { fileChanges.set(filename.toLowerCase(), 'new') changedFilesToPush.push(f) } else if (remoteSize !== localSize) { fileChanges.set(filename.toLowerCase(), 'changed') changedFilesToPush.push(f) } else { fileChanges.set(filename.toLowerCase(), 'unchanged') } } } const newFileNames = new Set(filesToPush.map((f) => (f.path.split('/').pop() ?? '').toLowerCase())) const deletedFileNames: string[] = [] const deletePaths: string[] = [] for (const [name] of remoteFileMap) { if (!newFileNames.has(name)) { deletedFileNames.push(name) deletePaths.push(`${folderPath}/${name}`) } } return { fileChanges, changedFilesToPush, deletedFileNames, deletePaths } }