mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
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:
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user