fix: adopt sharp 0.35.2+ by centralizing the FormatEnum key type (#362)

Centralizes the sharp format key type into a single `SharpFormat` alias derived from `toFormat()`'s signature (exported from @snapotter/image-engine, imported by the API consumers), replacing the duplicated `keyof FormatEnum` definitions. Adds convert/compress format round-trip tests covering webp/avif/png/jpeg. Part of #325.
This commit is contained in:
Matt Van Horn
2026-07-01 21:50:29 +08:00
committed by GitHub
parent 852f6cecba
commit 9052da27f3
4 changed files with 56 additions and 9 deletions
+2 -3
View File
@@ -1,6 +1,5 @@
import sharp, { type FormatEnum } from "sharp";
export type SharpFormat = Extract<keyof FormatEnum, string> | "avif";
import type { SharpFormat } from "@snapotter/image-engine";
import sharp from "sharp";
export interface OutputFormat {
format: SharpFormat;
+2 -3
View File
@@ -1,8 +1,9 @@
import { randomUUID } from "node:crypto";
import { extname } from "node:path";
import type { SharpFormat } from "@snapotter/image-engine";
import archiver from "archiver";
import type { FastifyInstance } from "fastify";
import sharp, { type FormatEnum } from "sharp";
import sharp from "sharp";
import { z } from "zod";
import { autoOrient } from "../../lib/auto-orient.js";
import { getSecurityHeaders } from "../../lib/csp.js";
@@ -24,8 +25,6 @@ const settingsSchema = z.object({
quality: z.number().min(1).max(100).default(90),
});
type SharpFormat = Extract<keyof FormatEnum, string> | "avif";
function resolveOutputFormat(
outputFormat: string,
originalExt: string,
+2 -2
View File
@@ -1,7 +1,7 @@
import type { ChannelStats, FormatEnum, Metadata, Sharp as SharpInstance } from "sharp";
import type { ChannelStats, Metadata, Sharp as SharpInstance } from "sharp";
export type Sharp = SharpInstance;
export type SharpFormat = Extract<keyof FormatEnum, string> | "avif";
export type SharpFormat = Extract<Parameters<Sharp["toFormat"]>[0], string>;
export type SharpChannelStats = ChannelStats;
export type SharpMetadata = Metadata;
+50 -1
View File
@@ -16,12 +16,41 @@ import { rotate } from "../src/operations/rotate.js";
import { saturation } from "../src/operations/saturation.js";
import { sepia } from "../src/operations/sepia.js";
import { stripMetadata } from "../src/operations/strip-metadata.js";
import type { OutputFormat, Sharp, SharpFormat } from "../src/types.js";
import { getImageInfo } from "../src/utils/metadata.js";
import { extToMime, formatToExt, formatToMime, mimeToExt } from "../src/utils/mime.js";
// Generate a 100x100 red PNG buffer for testing
let testBuffer: Buffer;
let testImage: () => sharp.Sharp;
let testImage: () => Sharp;
const SHARP_FORMAT_CASES = [
{ sharpFormat: "webp", operationFormat: "webp", expectedFormat: "webp" },
{ sharpFormat: "avif", operationFormat: "avif", expectedFormat: "avif" },
{ sharpFormat: "png", operationFormat: "png", expectedFormat: "png" },
{ sharpFormat: "jpeg", operationFormat: "jpg", expectedFormat: "jpeg" },
] as const satisfies ReadonlyArray<{
sharpFormat: SharpFormat;
operationFormat: OutputFormat;
expectedFormat: string;
}>;
function tinyTestImage(): Sharp {
return sharp({
create: {
width: 4,
height: 4,
channels: 3,
background: { r: 255, g: 0, b: 0 },
},
});
}
async function expectOutputFormat(buffer: Buffer, expectedFormat: string): Promise<void> {
const meta = await sharp(buffer).metadata();
const format = meta.format === "heif" ? "avif" : meta.format;
expect(format).toBe(expectedFormat);
}
beforeAll(async () => {
testBuffer = await sharp({
@@ -212,6 +241,26 @@ describe("compress", () => {
});
});
describe("sharp format compatibility", () => {
it.each(SHARP_FORMAT_CASES)("convert accepts $sharpFormat", async ({
operationFormat,
expectedFormat,
}) => {
const result = await convert(tinyTestImage(), { format: operationFormat });
const buf = await result.toBuffer();
await expectOutputFormat(buf, expectedFormat);
});
it.each(SHARP_FORMAT_CASES)("compress accepts $sharpFormat", async ({
operationFormat,
expectedFormat,
}) => {
const result = await compress(tinyTestImage(), { quality: 80, format: operationFormat });
const buf = await result.toBuffer();
await expectOutputFormat(buf, expectedFormat);
});
});
describe("grayscale", () => {
it("should convert to grayscale", async () => {
const result = await grayscale(testImage());