From 9ee22db8bae754a436ee47e85a56cc43f561bc08 Mon Sep 17 00:00:00 2001 From: openhands Date: Sun, 27 Sep 2026 16:00:44 +0200 Subject: [PATCH] fix(nitro): normalise attached and recovered .nitro bundles too Two more .nitro entry points in the main import path still wrote the supplied buffer verbatim: an attached `providedNitro` and a bundle pulled back by `resolveMissingNitro`. Both are real furniture imports, so they could still land a PNG texture while the SWF, clone and upload paths produced WebP. Route both through the same normalisation, falling back to the original bytes with a warning if the texture cannot be decoded. --- src/lib/services/furni-import.ts | 33 ++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/lib/services/furni-import.ts b/src/lib/services/furni-import.ts index 7c0c7a8c..c5441139 100644 --- a/src/lib/services/furni-import.ts +++ b/src/lib/services/furni-import.ts @@ -218,6 +218,26 @@ import { classifyFurni } from "@/lib/furni/auto-catalog"; export { autoPriceFurni } from "@/lib/furni/auto-catalog"; import { autoPriceFurni } from "@/lib/furni/auto-catalog"; +import { toWebpLosslessBundle } from "@/lib/services/swf/nitro-builder"; + +/** + * Store an incoming .nitro as WebP Lossless, whatever format it arrived in, so + * every import path ends up with the same on-disk format as the SWF importer. + * A texture that cannot be decoded is kept as-is with a warning: the bundle + * itself is valid, and dropping a furniture item over a codec edge case is + * worse than a slightly larger texture. + */ +async function toWebpLosslessOrKeep( + bundle: Buffer, + warnings: string[], +): Promise { + try { + return await toWebpLosslessBundle(bundle); + } catch { + warnings.push("texture kept in its original format"); + return bundle; + } +} /** Get or create a category sub-page under the imported parent page. * Caches category page IDs in-memory for CATEGORY_CACHE_TTL_MS to avoid @@ -654,7 +674,11 @@ export async function importSingleFurni(params: { const mirrorNitroDirs = assetTargets.mirrorDirs.map((d) => d.nitroDir); if (params.providedNitro && !existsSync(nitroPath)) { - await fs.writeFile(nitroPath, params.providedNitro, { flag: "wx" }); + await fs.writeFile( + nitroPath, + await toWebpLosslessOrKeep(params.providedNitro, warnings), + { flag: "wx" }, + ); } // ── Download assets in parallel (with retry + validation) ──────── @@ -816,7 +840,12 @@ export async function importSingleFurni(params: { if (!existsSync(nitroPath) && params.resolveMissingNitro) { await onProgress?.("checking_sources"); const recovered = await params.resolveMissingNitro(rev); - if (recovered) await fs.writeFile(nitroPath, recovered, { flag: "wx" }); + if (recovered) + await fs.writeFile( + nitroPath, + await toWebpLosslessOrKeep(recovered, warnings), + { flag: "wx" }, + ); else warnings.push( "No matching bundle was recovered from the checked sources. Review source availability or attach the original .nitro file.",