upatde: dockerfile init blender
This commit is contained in:
+118
-27
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user