diff --git a/hooks/useUploadOrchestrator.ts b/hooks/useUploadOrchestrator.ts index 2f45e25..0e5ae77 100644 --- a/hooks/useUploadOrchestrator.ts +++ b/hooks/useUploadOrchestrator.ts @@ -10,27 +10,26 @@ import type { FileDiff } from '@/lib/types' import { checkFolderDiffs, stageUpload, uploadDrive, uploadGit } from '@/lib/upload-api' import type { CheckResult } from '@/lib/upload-api' -const UPLOAD_LOG_PREFIX = '[upload-gltf]' - function formatElapsed(startedAt: number) { return `${((performance.now() - startedAt) / 1000).toFixed(1)}s` } -function startTimedLog(label: string, details?: Record) { +function logUpload(level: 'INFO' | 'ERROR', step: string, action: string, startedAt: number, details?: Record) { + const log = level === 'ERROR' ? console.error : console.info + log(`[${level}] ${step} -> ${action} | Timer: ${formatElapsed(startedAt)}`, details || '') +} + +function startTimedLog(step: string, action: string, details?: Record) { const startedAt = performance.now() - console.info(`${UPLOAD_LOG_PREFIX} ${label} started`, details || '') + logUpload('INFO', step, `${action} started`, startedAt, details) const interval = window.setInterval(() => { - console.info(`${UPLOAD_LOG_PREFIX} ${label} still running`, { elapsed: formatElapsed(startedAt), ...details }) + logUpload('INFO', step, `${action} running`, startedAt, details) }, 10_000) return (status: 'done' | 'failed' | 'cancelled' = 'done', extra?: Record) => { window.clearInterval(interval) - console.info(`${UPLOAD_LOG_PREFIX} ${label} ${status}`, { - elapsed: formatElapsed(startedAt), - ...details, - ...extra, - }) + logUpload(status === 'failed' ? 'ERROR' : 'INFO', step, `${action} ${status}`, startedAt, { ...details, ...extra }) } } @@ -83,7 +82,7 @@ export function useUploadOrchestrator({ } const folderName = entriesRef.current[index]?.folderName - const endGitLog = startTimedLog('Git upload', { folderName, stagingId }) + const endGitLog = startTimedLog('Git', 'Upload', { folderName, stagingId }) let gitResult: Awaited> try { @@ -145,7 +144,7 @@ export function useUploadOrchestrator({ driveError: undefined, }) - const endDriveLog = startTimedLog('Drive upload', { + const endDriveLog = startTimedLog('Drive', 'Upload', { folderName: folderEntry.folderName, stagingId, action: driveAction, @@ -206,7 +205,7 @@ export function useUploadOrchestrator({ abortRef.current = controller try { - const endStageLog = startTimedLog('Verification: staging upload', { + const endStageLog = startTimedLog('Verification', 'Staging', { folderName: folder.folderName, files: 1 + folder.textures.length, modelSize: folder.modelFile.size, @@ -225,7 +224,7 @@ export function useUploadOrchestrator({ stagingIdRef.current = staged.stagingId - const endCheckLog = startTimedLog('Verification: GitHub diff check', { + const endCheckLog = startTimedLog('Verification', 'GitHub diff', { folderName: folder.folderName, stagingId: staged.stagingId, }) diff --git a/lib/github.ts b/lib/github.ts index 0cf46bd..d8a1fc8 100644 --- a/lib/github.ts +++ b/lib/github.ts @@ -3,6 +3,8 @@ import { Octokit } from '@octokit/rest' import { LFS_EXTENSIONS } from './constants' import type { PushFile, RemoteFile } from './types' +const LFS_BATCH_SIZE = 100 + // --------------------------------------------------------------------------- // Octokit helpers // --------------------------------------------------------------------------- @@ -55,6 +57,24 @@ function parseLfsPointer(content: string): { oid: string; size: number } | null return { oid: oidMatch[1], size: parseInt(sizeMatch[1], 10) } } +function formatElapsed(startedAt: number) { + return `${((performance.now() - startedAt) / 1000).toFixed(1)}s` +} + +function logInfo(step: string, action: string, startedAt: number, details?: Record) { + console.info(`[INFO] ${step} -> ${action} | Timer: ${formatElapsed(startedAt)}`, details || '') +} + +function chunkArray(items: T[], size: number) { + const chunks: T[][] = [] + + for (let i = 0; i < items.length; i += size) { + chunks.push(items.slice(i, i + size)) + } + + return chunks +} + interface LfsObject { oid: string size: number @@ -76,6 +96,25 @@ async function uploadToLfs( ): Promise { if (objects.length === 0) return + const batches = chunkArray(objects, LFS_BATCH_SIZE) + + for (let i = 0; i < batches.length; i++) { + await uploadToLfsBatch(owner, repo, batches[i], i + 1, batches.length) + } +} + +async function uploadToLfsBatch( + owner: string, + repo: string, + objects: LfsObject[], + batchNumber: number, + totalBatches: number, +): Promise { + const startedAt = performance.now() + logInfo('Git LFS', `Batch ${batchNumber}/${totalBatches} started`, startedAt, { + objects: objects.length, + }) + const token = process.env.GITHUB_TOKEN! const lfsUrl = `https://github.com/${owner}/${repo}.git/info/lfs/objects/batch` @@ -96,6 +135,10 @@ async function uploadToLfs( if (!batchRes.ok) { const text = await batchRes.text() + console.error(`[ERROR] Git LFS -> Batch ${batchNumber}/${totalBatches} failed | Timer: ${formatElapsed(startedAt)}`, { + objects: objects.length, + status: batchRes.status, + }) throw new Error(`LFS batch request failed (${batchRes.status}): ${text}`) } @@ -165,6 +208,10 @@ async function uploadToLfs( } } } + + logInfo('Git LFS', `Batch ${batchNumber}/${totalBatches} done`, startedAt, { + objects: objects.length, + }) } // ---------------------------------------------------------------------------