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";
|
import type { SharpFormat } from "@snapotter/image-engine";
|
||||||
|
import sharp from "sharp";
|
||||||
export type SharpFormat = Extract<keyof FormatEnum, string> | "avif";
|
|
||||||
|
|
||||||
export interface OutputFormat {
|
export interface OutputFormat {
|
||||||
format: SharpFormat;
|
format: SharpFormat;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { randomUUID } from "node:crypto";
|
import { randomUUID } from "node:crypto";
|
||||||
import { extname } from "node:path";
|
import { extname } from "node:path";
|
||||||
|
import type { SharpFormat } from "@snapotter/image-engine";
|
||||||
import archiver from "archiver";
|
import archiver from "archiver";
|
||||||
import type { FastifyInstance } from "fastify";
|
import type { FastifyInstance } from "fastify";
|
||||||
import sharp, { type FormatEnum } from "sharp";
|
import sharp from "sharp";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { autoOrient } from "../../lib/auto-orient.js";
|
import { autoOrient } from "../../lib/auto-orient.js";
|
||||||
import { getSecurityHeaders } from "../../lib/csp.js";
|
import { getSecurityHeaders } from "../../lib/csp.js";
|
||||||
@@ -24,8 +25,6 @@ const settingsSchema = z.object({
|
|||||||
quality: z.number().min(1).max(100).default(90),
|
quality: z.number().min(1).max(100).default(90),
|
||||||
});
|
});
|
||||||
|
|
||||||
type SharpFormat = Extract<keyof FormatEnum, string> | "avif";
|
|
||||||
|
|
||||||
function resolveOutputFormat(
|
function resolveOutputFormat(
|
||||||
outputFormat: string,
|
outputFormat: string,
|
||||||
originalExt: 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 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 SharpChannelStats = ChannelStats;
|
||||||
export type SharpMetadata = Metadata;
|
export type SharpMetadata = Metadata;
|
||||||
|
|
||||||
|
|||||||
@@ -16,12 +16,41 @@ import { rotate } from "../src/operations/rotate.js";
|
|||||||
import { saturation } from "../src/operations/saturation.js";
|
import { saturation } from "../src/operations/saturation.js";
|
||||||
import { sepia } from "../src/operations/sepia.js";
|
import { sepia } from "../src/operations/sepia.js";
|
||||||
import { stripMetadata } from "../src/operations/strip-metadata.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 { getImageInfo } from "../src/utils/metadata.js";
|
||||||
import { extToMime, formatToExt, formatToMime, mimeToExt } from "../src/utils/mime.js";
|
import { extToMime, formatToExt, formatToMime, mimeToExt } from "../src/utils/mime.js";
|
||||||
|
|
||||||
// Generate a 100x100 red PNG buffer for testing
|
// Generate a 100x100 red PNG buffer for testing
|
||||||
let testBuffer: Buffer;
|
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 () => {
|
beforeAll(async () => {
|
||||||
testBuffer = await sharp({
|
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", () => {
|
describe("grayscale", () => {
|
||||||
it("should convert to grayscale", async () => {
|
it("should convert to grayscale", async () => {
|
||||||
const result = await grayscale(testImage());
|
const result = await grayscale(testImage());
|
||||||
|
|||||||
Reference in New Issue
Block a user