diff --git a/.changeset/fresh-chairs-say.md b/.changeset/fresh-chairs-say.md new file mode 100644 index 00000000..b4fa79d5 --- /dev/null +++ b/.changeset/fresh-chairs-say.md @@ -0,0 +1,5 @@ +--- +"@imgproxy/imgproxy-js-core": minor +--- + +Add support for `onlyPresets` setting to generate [presets-only urls](https://docs.imgproxy.net/usage/presets#only-presets) diff --git a/src/methods/generateImageInfoUrl.test.ts b/src/methods/generateImageInfoUrl.test.ts index ab51a4e8..ec3c932c 100644 --- a/src/methods/generateImageInfoUrl.test.ts +++ b/src/methods/generateImageInfoUrl.test.ts @@ -86,4 +86,32 @@ describe("generateImageInfoUrl", () => { "/avg:t:f/do:t/dc:t:t/exp:1729409825/iptc:t/p:6/pr:test:test2/s:t/plain/https://example.com/host/pic.png" ); }); + + it("should work with `onlyPresets` setting", () => { + expect( + generateUrl( + { value: "https://example.com/host/pic.png", type: "plain" }, + { + preset: ["test", "test2"], + }, + { onlyPresets: true } + ) + ).toEqual("/test:test2/plain/https://example.com/host/pic.png"); + }); + + it("should ignore other options with `onlyPresets` setting", () => { + expect( + generateUrl( + { value: "https://example.com/host/pic.png", type: "plain" }, + { + preset: ["test", "test2"], + crop: { + width: 100, + height: 100, + }, + }, + { onlyPresets: true } + ) + ).toEqual("/test:test2/plain/https://example.com/host/pic.png"); + }); }); diff --git a/src/methods/generateImageInfoUrl.ts b/src/methods/generateImageInfoUrl.ts index 337f2506..f7f90288 100644 --- a/src/methods/generateImageInfoUrl.ts +++ b/src/methods/generateImageInfoUrl.ts @@ -1,6 +1,7 @@ import { OptionsImageInfo } from "../typesImageInfo"; import * as optionModules from "../optionsImageInfo"; import { guardIsUndef, guardIsValidVal } from "../utils"; +import { Settings } from "../settings"; const correctUrlTypes = { plain: true, @@ -13,9 +14,13 @@ export type URLImageInfo = { type: "plain" | "base64" | "encrypted"; }; +const allModules = Object.values(optionModules); +const presetOnlyModule = [optionModules.preset]; + const generateImageInfoUrl = ( url: URLImageInfo, - options?: OptionsImageInfo + options?: OptionsImageInfo, + settings?: Settings ): string => { guardIsUndef(url.value, "url.value", "Must be a string"); guardIsUndef( @@ -27,10 +32,12 @@ const generateImageInfoUrl = ( let optsPart = ""; if (options) { - for (const [, optionModule] of Object.entries(optionModules)) { + const modules = settings?.onlyPresets ? presetOnlyModule : allModules; + + for (const optionModule of modules) { if (optionModule.test(options)) { optsPart += "/"; - optsPart += optionModule.build(options); + optsPart += optionModule.build(options, settings); } } } diff --git a/src/methods/generateUrl.test.ts b/src/methods/generateUrl.test.ts index 85fc4fb6..80d31f32 100644 --- a/src/methods/generateUrl.test.ts +++ b/src/methods/generateUrl.test.ts @@ -70,4 +70,27 @@ describe("generateUrl", () => { "/bl:5/el:t/ex:t:nowe::5/f:webp/h:150/q:80/w:150/z:1.5/plain/https://example.com/host/pic.png" ); }); + + it("should work with `onlyPresets` setting", () => { + expect( + generateUrl( + { value: "https://example.com/host/pic.png", type: "plain" }, + { + preset: ["preset1", "preset2"], + width: 150, + height: 150, + format: "webp", + quality: 80, + enlarge: "t", + extend: { + extend: 1, + gravity: { type: "nowe", y_offset: 5 }, + }, + blur: 5, + zoom: 1.5, + }, + { onlyPresets: true } + ) + ).toEqual("/preset1:preset2/plain/https://example.com/host/pic.png"); + }); }); diff --git a/src/methods/generateUrl.ts b/src/methods/generateUrl.ts index 7a4b589c..c88b4e46 100644 --- a/src/methods/generateUrl.ts +++ b/src/methods/generateUrl.ts @@ -2,6 +2,7 @@ import { Options } from "../types"; import * as optionModules from "../options"; import { guardIsUndef, guardIsValidVal } from "../utils"; import type { URLImageInfo } from "./generateImageInfoUrl"; +import { Settings } from "../settings"; const correctUrlTypes = { plain: true, @@ -9,7 +10,14 @@ const correctUrlTypes = { encrypted: true, }; -const generateUrl = (url: URLImageInfo, options?: Options): string => { +const allModules = Object.values(optionModules); +const presetOnlyModule = [optionModules.preset]; + +const generateUrl = ( + url: URLImageInfo, + options?: Options, + settings?: Settings +): string => { guardIsUndef(url.value, "url.value", "Must be a string"); guardIsUndef( url.type, @@ -20,10 +28,12 @@ const generateUrl = (url: URLImageInfo, options?: Options): string => { let optsPart = ""; if (options) { - for (const [, optionModule] of Object.entries(optionModules)) { + const modules = settings?.onlyPresets ? presetOnlyModule : allModules; + + for (const optionModule of modules) { if (optionModule.test(options)) { optsPart += "/"; - optsPart += optionModule.build(options); + optsPart += optionModule.build(options, settings); } } } diff --git a/src/optionsShared/preset.ts b/src/optionsShared/preset.ts index df83b954..57c85870 100644 --- a/src/optionsShared/preset.ts +++ b/src/optionsShared/preset.ts @@ -1,3 +1,4 @@ +import { Settings } from "../settings"; import { Preset, PresetOptionsPartial } from "../typesShared/preset"; import { guardIsUndef, guardIsNotArray } from "../utils"; @@ -7,8 +8,8 @@ const getOpt = (options: PresetOptionsPartial): Preset | undefined => const test = (options: PresetOptionsPartial): boolean => Boolean(getOpt(options)); -const build = (options: PresetOptionsPartial): string => { - const preset = getOpt(options); +const build = (options: PresetOptionsPartial, settings?: Settings): string => { + let preset = getOpt(options); guardIsUndef(preset, "preset"); guardIsNotArray(preset, "preset"); @@ -17,7 +18,11 @@ const build = (options: PresetOptionsPartial): string => { throw new Error("preset option should contain only strings"); } - return `pr:${preset.join(":")}`; + if (!settings?.onlyPresets) { + preset = ["pr", ...preset]; + } + + return `${preset.join(":")}`; }; export { test, build }; diff --git a/src/settings.ts b/src/settings.ts new file mode 100644 index 00000000..fc369d00 --- /dev/null +++ b/src/settings.ts @@ -0,0 +1,7 @@ +export type Settings = { + /** + * Setting `onlyPresets` to true switches imgproxy into presets-only mode. In this mode, imgproxy accepts a presets list as processing options. + * @see https://docs.imgproxy.net/usage/presets#only-presets + */ + onlyPresets?: boolean; +}; diff --git a/tests/optionsShared/preset.test.ts b/tests/optionsShared/preset.test.ts index 9f66c5db..aaec1bc1 100644 --- a/tests/optionsShared/preset.test.ts +++ b/tests/optionsShared/preset.test.ts @@ -46,5 +46,11 @@ describe("preset", () => { it("should return 'pr:paddings:jpg' if pr option is ['paddings', 'jpg']", () => { expect(build({ pr: ["paddings", "jpg"] })).toEqual("pr:paddings:jpg"); }); + + it("should work with `onlyPresets` setting ", () => { + expect(build({ pr: ["paddings", "jpg"] }, { onlyPresets: true })).toEqual( + "paddings:jpg" + ); + }); }); });