upatde: dockerfile init blender

This commit is contained in:
Tom Boullay
2026-04-14 14:06:04 +02:00
parent 2b3d02e489
commit ab9685b6ee
5 changed files with 264 additions and 65 deletions
+118 -27
View File
@@ -5,6 +5,7 @@ import { mkdir, writeFile, readFile, unlink, rm } from 'fs/promises'
import { existsSync } from 'fs'
import { execFile } from 'child_process'
import { promisify } from 'util'
import { createHash } from 'crypto'
const execFileAsync = promisify(execFile)
@@ -46,6 +47,13 @@ function parseRepoUrl(): { owner: string; repo: string } {
return { owner: match[1], repo: match[2] }
}
/** Compute the SHA that Git would assign to a blob with this content */
function computeGitBlobSha(content: Buffer): string {
const header = `blob ${content.length}\0`
const store = Buffer.concat([Buffer.from(header), content])
return createHash('sha1').update(store).digest('hex')
}
// ---------------------------------------------------------------------------
// Blender Draco compression
// ---------------------------------------------------------------------------
@@ -94,29 +102,60 @@ function buildCommitMessage(
modelFilename: string,
textureNames: string[],
compressed: boolean,
isReplace: boolean,
fileChanges: Map<string, 'new' | 'changed' | 'unchanged'>,
deletedFileNames: string[],
): string {
const title = `update: upload-gltf add a new model -> ${destination}/${folderName}`
const title = isReplace
? `update: upload-gltf update -> ${destination}/${folderName}`
: `update: upload-gltf add a new model -> ${destination}/${folderName}`
const lines: string[] = [title, '']
// Model section — only show if changed or new
const modelChange = fileChanges.get(modelFilename.toLowerCase())
if (modelChange === 'new') {
lines.push('📦 Model')
lines.push(`${modelFilename}${compressed ? ' (compressed)' : ''}`)
} else if (modelChange === 'changed') {
lines.push('📦 Model')
lines.push(` 🔄 ${modelFilename}${compressed ? ' (compressed)' : ''}`)
}
// unchanged → don't show model section at all
// Textures section — only show lines that have changes
const foundTextures = new Set(
textureNames.map(t => t.toLowerCase().replace(/\.[^.]+$/, ''))
)
const textureLines = REQUIRED_TEXTURES.map(tex => {
if (foundTextures.has(tex)) {
const actual = textureNames.find(t => t.toLowerCase().replace(/\.[^.]+$/, '') === tex)
return `${actual}`
}
return `${tex} (manquant)`
})
const textureLines: string[] = []
const lines = [
title,
'',
'📦 Model',
`${modelFilename}${compressed ? ' (compressed)' : ''}`,
'🎨 Textures',
...textureLines,
]
// Changed or new textures
for (const tex of REQUIRED_TEXTURES) {
if (foundTextures.has(tex)) {
const actual = textureNames.find(t => t.toLowerCase().replace(/\.[^.]+$/, '') === tex)!
const change = fileChanges.get(actual.toLowerCase())
if (change === 'new') {
textureLines.push(`${actual}`)
} else if (change === 'changed') {
textureLines.push(` 🔄 ${actual}`)
}
// unchanged → skip
} else if (!isReplace) {
// Only show missing textures for new uploads
textureLines.push(`${tex} (manquant)`)
}
}
// Deleted files (orphans removed from remote)
for (const name of deletedFileNames) {
textureLines.push(`${name} (supprime)`)
}
if (textureLines.length > 0) {
lines.push('🎨 Textures')
lines.push(...textureLines)
}
return lines.join('\n')
}
@@ -313,7 +352,7 @@ export async function GET(req: NextRequest) {
})
if (Array.isArray(data)) {
const existingFiles = data.map(f => f.name)
const existingFiles = data.map(f => ({ name: f.name, sha: f.sha }))
return NextResponse.json({
success: true,
exists: true,
@@ -412,12 +451,9 @@ export async function POST(req: NextRequest) {
})
}
// --- Build commit message ---
const commitMessage = buildCommitMessage(folderName, destination, modelFilename, textureNames, compressed)
// --- Detect existing files to clean up orphans ---
// --- Detect existing files and compare SHA to classify changes ---
const folderPath = `public/models/${destination}/${folderName}`
let existingFilePaths: string[] = []
const remoteFileMap = new Map<string, string>() // name -> sha
try {
const octokit = getOctokit()
@@ -432,23 +468,78 @@ export async function POST(req: NextRequest) {
})
if (Array.isArray(data)) {
existingFilePaths = data.map(f => `${folderPath}/${f.name}`)
for (const f of data) {
remoteFileMap.set(f.name.toLowerCase(), f.sha)
}
}
} catch {
// 404 = folder doesn't exist yet, no cleanup needed
// 404 = folder doesn't exist yet
}
const isReplace = remoteFileMap.size > 0
// Classify each file: changed, new, or unchanged
type FileChange = 'new' | 'changed' | 'unchanged'
const fileChanges = new Map<string, FileChange>()
const changedFilesToPush: { path: string; contentBase64: string }[] = []
for (const f of filesToPush) {
const filename = f.path.split('/').pop()!
const localSha = computeGitBlobSha(Buffer.from(f.contentBase64, 'base64'))
const remoteSha = remoteFileMap.get(filename.toLowerCase())
if (!remoteSha) {
fileChanges.set(filename.toLowerCase(), 'new')
changedFilesToPush.push(f)
} else if (remoteSha !== localSha) {
fileChanges.set(filename.toLowerCase(), 'changed')
changedFilesToPush.push(f)
} else {
fileChanges.set(filename.toLowerCase(), 'unchanged')
// skip — don't push unchanged files
}
}
// Files on remote that are not in the new upload → will be deleted (orphans)
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}`)
}
}
// If nothing changed at all, don't create an empty commit
if (changedFilesToPush.length === 0 && deletePaths.length === 0) {
return NextResponse.json({
success: true,
folderName,
filesCount: 0,
compressed,
compressionError: compressionError || undefined,
message: 'Aucun fichier modifie — rien a envoyer.',
})
}
// --- Build commit message ---
const commitMessage = buildCommitMessage(
folderName, destination, modelFilename, textureNames,
compressed, isReplace, fileChanges, deletedFileNames,
)
// --- Push all in one commit ---
try {
const { commitUrl } = await pushAllToGitHub(folderName, filesToPush, existingFilePaths, commitMessage)
const { commitUrl } = await pushAllToGitHub(folderName, changedFilesToPush, deletePaths, commitMessage)
return NextResponse.json({
success: true,
folderName,
filesCount: filesToPush.length,
filesCount: changedFilesToPush.length,
compressed,
compressionError: compressionError || undefined,
message: `${filesToPush.length} fichier(s) envoye(s) sur GitHub en un seul commit.`,
message: `${changedFilesToPush.length} fichier(s) modifie(s) envoye(s) sur GitHub en un seul commit.`,
commitUrl,
})
} catch (err) {