fix(nitro): repair stale meta.image in bundles that are already lossless

A bundle whose texture is already VP8L was returned untouched, so a
stale spritesheet.meta.image survived the normalisation and the client
could not find the texture member. Rebuild the archive in that case and
reuse the existing VP8L bytes instead of decoding them again.
This commit is contained in:
openhands committed 2026-09-27 17:13:52 +02:00
1 parent 9ee22db8ba
commit d176fad4da
2 files changed
+33 -2

No files matched your search

+17 -2
View File
@@ -520,14 +520,29 @@ function inflateJsonOrGzip(
* renamed. `meta.image` is corrected by `createNitroBundle`.
*
* A bundle that is already lossless WebP is returned untouched, which keeps
* the operation idempotent and avoids a pointless re-encode.
* the operation idempotent and avoids a pointless re-encode — but only when
* its metadata is already consistent. A bundle whose `meta.image` names a
* member that is not in the file is still rebuilt, because a client that
* cannot find the texture renders nothing and a re-encode is not what fixes
* that.
*
* Throws if the texture claims to be PNG or WebP but cannot actually be
* decoded; callers that would rather keep the original should catch.
*/
export async function toWebpLosslessBundle(bundle: Buffer): Promise<Buffer> {
const parsed = parseNitroBundle(bundle);
if (isLosslessWebp(parsed.texture)) return bundle;
const meta = (parsed.json as { spritesheet?: { meta?: { image?: string } } })
.spritesheet?.meta;
if (isLosslessWebp(parsed.texture)) {
if (meta?.image === parsed.textureFileName) return bundle;
// Stale metadata only: rebuild the archive with the pointer corrected
// and reuse the existing VP8L bytes rather than re-encoding them.
return createNitroBundle(
parsed.json,
parsed.texture,
parsed.textureFileName.replace(/\.(?:png|webp)$/i, ""),
);
}
const image = sharp(parsed.texture);
const { width = 0, height = 0 } = await image.metadata();
+16
View File
@@ -223,6 +223,22 @@ describe("normalising a bundle to WebP Lossless", () => {
);
await expect(toWebpLosslessBundle(broken)).rejects.toThrow();
});
it("repairs stale meta.image in a bundle that is already lossless", async () => {
// Already VP8L, so there is nothing to re-encode — but the pointer
// names a member the archive does not contain, which a re-encode
// would not be what fixes.
const stale = await bundleWith("webp", "chair.png");
expect(isLosslessWebp(parseNitroBundle(stale).texture)).toBe(true);
const after = parseNitroBundle(await toWebpLosslessBundle(stale));
expect(after.textureFileName).toBe("chair.webp");
expect(
(after.json.spritesheet as { meta: { image: string } }).meta.image,
).toBe("chair.webp");
// The existing VP8L bytes are reused rather than decoded again.
expect(after.texture.equals(parseNitroBundle(stale).texture)).toBe(true);
});
});
describe("spritesheet encoding", () => {